enqueue/null
Null transport implementation for Queue Interop that discards messages instead of sending them anywhere. Use it as a mock or no-op queue in tests and development to verify message flow without a real broker.
Install the transport alongside the Enqueue context you'll be using in Laravel:
composer require enqueue/null
The package is tiny — it ships a NullTransportFactory, NullTransport, NullTopic, and a NullConsumer that all live in Enqueue\Null. There is no service provider; it plugs into the php-enqueue/enqueue abstraction, not Laravel's own QUEUE_CONNECTION config. Two things to internalize before writing a line of code:
This is not QUEUE_CONNECTION=null. Laravel ships its own null driver (which literally does nothing) and a sync driver (which runs jobs inline). enqueue/null is the null transport for the Enqueue ecosystem. It only matters if you're already using enqueue/*, enqueue/laravel, or enqueue/simple-client for messaging. If you're on Laravel's default queue, this package adds nothing — use sync or null instead.
It is a contract-preserving stub. Every call returns the transport's own dummy Message/Context objects so your producer, consumer, and extension code can run end-to-end without a broker.
The first use case: swap your Redis/RabbitMQ transport for the null transport in tests so producer code can be executed without a live broker.
use Enqueue\Null\NullConnectionFactory;
use Enqueue\Null\NullTransportFactory;
use Interop\Queue\Context;
// Typically in a test bootstrap or a test service provider
$factory = new NullTransportFactory();
$context = $factory->createContext();
$this->app->instance(Context::class, $context);
Look first at NullContext, NullProducer, NullConsumer in src/. The whole surface is a few hundred lines — read it in one sitting and you'll know exactly what it does (and doesn't) do.
Pattern 1: Transport swap in tests. Bind the Context interface in a test service provider and let the rest of your app resolve it normally. This keeps production code free of if (app()->environment()) branching.
// app/Providers/EnqueueServiceProvider.php
public function register(): void
{
$this->app->singleton(Context::class, function () {
$factory = app()->environment('testing')
? new NullTransportFactory()
: new AmqpConnectionFactory(config('enqueue.dsn'));
return $factory->createContext();
});
}
Pattern 2: Asserting producer behavior without a broker. Because NullProducer::send() returns a NullMessage, you can assert on the message your code produced before it hits the void. Use a spy decorator rather than asserting on the null transport itself — the null transport will never tell you what was sent.
final class SpyProducer implements Producer
{
public array $sent = [];
public function __construct(private Producer $inner) {}
public function send(Message $message, array $extensions = []): void
{
$this->sent[] = $message;
$this->inner->send($message, $extensions);
}
// ...
}
Pattern 3: Local dev with enqueue/simple-client. If you're using the Simple Client abstraction for dispatching jobs, point the client at a null transport locally so devs don't need Docker, and let CI run against a real broker. This is the sweet spot for this package — you keep the exact same client API, just no wire traffic.
Pattern 4: CI fan-out. In a matrix build, run most jobs against NullTransportFactory and reserve one "integration" job for a service container (RabbitMQ, Redis, SQS). The null runs finish in milliseconds and validate your application wiring; the real run validates broker interaction.
Pattern 5: Consumer contract tests. NullConsumer::receive() returns null immediately (non-blocking). That's actually useful — you can write a test that drives a consumer loop to termination in one iteration and verifies it exits cleanly.
Integration tip: If you want real Laravel queue integration (Horizon, dispatch(), ShouldQueue), don't reach for this package. Use Laravel's Queue::fake() for assertions and the sync driver for behaviour. Reserve enqueue/null for Enqueue-based code paths.
NullProducer::send() succeeds unconditionally. If your code assumes a downstream ack, it will look fine in tests and fail in production when the real broker refuses the message.Queue::fake()/Queue::assertPushed() if you're on Laravel's queue.receive() returns null, not a message. Consumer loops written with $context->createConsumer($queue)->receive(1000) will see an immediate null. Any while (true) { $msg = receive(); ... } loop will spin hot if you forget to break on null. Add a bounded iteration or timeout in tests.NullProducer::send() accepts the $extensions array but ignores each one. If you rely on DelayExtension, PriorityExtension, or MessageIdExtension semantics, those are no-ops here. Test them against a real transport.createQueue/createTopic are inert. Queues and topics returned by NullContext have no lifecycle — no declare, no purge, no broker-side existence check. Code that calls $context->createQueue('foo')->setArgument(...) will not throw, but nothing happens.null:// registration in enqueue/simple-client's factory resolver by default in older versions. If you get Could not find factory for scheme "null", register new NullConnectionFactory() explicitly in your connection factory collection.enqueue/null out of the monorepo around v0.9/0.10. composer show enqueue/null should match the rest of your enqueue/* versions; mismatches cause interface-not-found errors at runtime that are painful to diagnose because they surface from a container binding, not the class you thought you were touching.Interop\Queue\Producer/Consumer and wrap either the null transport or the real one. Don't fork the package — it's deliberately tiny and any behaviour you add belongs in your own test scaffolding.enqueue/fs (filesystem transport). Same API, messages land in a directory, delete afterwards. This is a much better debugging story than logging inside NullProducer.How can I help you explore Laravel packages today?