Running Laravel tests with PHPUnit, queued jobs seem to always run in sync mode. How to dispatch them in standard mode?
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Running Laravel Jobs Asynchronously in PHPUnit: Solving the Sync/Async Testing Dilemma
As developers working with modern web applications built on Laravel, managing background jobs is crucial. The ability to dispatch tasks asynchronously—where the API call returns immediately while the work happens in the background—is a core feature of modern architecture. However, when we move from live application behavior to automated testing using PHPUnit, we often encounter a frustrating paradox: our tests seem to force synchronous execution, making it difficult to accurately simulate real-world asynchronous flows.
This post dives into the specific issue where Laravel queue jobs appear to run in sync mode during PHPUnit testing, even when the application is configured to use a driver like database. We will explore why this happens and provide practical solutions to ensure your tests properly validate asynchronous job dispatching.
The Synchronization Problem: Why Jobs Block Tests
The core issue lies in how the dispatch() method interacts with the underlying queue driver during testing environments. When you call $job->dispatch() within a PHPUnit test, if the setup doesn't explicitly handle the queue interaction asynchronously, Laravel often waits for the job to be successfully placed into the queue storage (in this case, the database table).
In a live environment, the HTTP request handler is designed to respond instantly, regardless of what happens in the background worker. In a synchronous test, however, the execution flow pauses at dispatch() until the framework confirms the job insertion, which can feel like a blocking operation, defeating the purpose of testing asynchronous behavior. You are observing the mechanism's internal synchronization rather than the intended external workflow.
Achieving Asynchronous Testing with PHPUnit
To properly test that your application correctly delegates work to the queue without waiting for the actual worker process to complete during the test run, we need to adjust our testing strategy. Simply dispatching a job is not enough; we need to ensure the test environment reflects non-blocking behavior.
The solution often involves leveraging Laravel's testing utilities and ensuring that any necessary waiting mechanisms are handled appropriately, or by mocking the queue interaction if the goal is strictly unit testing the controller logic rather than the queue itself.
Method 1: Simulating Asynchronous Dispatch (Recommended)
For integration tests involving queues, the most robust method involves setting up expectations around the dispatch process. While you cannot truly make the external queue worker run asynchronously within a single PHPUnit test thread without spawning a separate process, you can ensure that your test verifies the correct state change occurred immediately.
If you are testing an endpoint that triggers a job, focus your assertion on the immediate result: verifying that the job record exists in the database and that the HTTP response was sent instantly.
Here is an example focusing on ensuring the dispatch itself happens correctly within the test context:
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Bus;
use Tests\TestCase;
class JobDispatchTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function job_is_dispatched_asynchronously()
{
// Setup: Simulate an API call or controller action that dispatches a job.
$job = new \App\Jobs\ProcessData;
// Dispatch the job
Bus::dispatch($job);
// Assertion 1: Verify the job is in the queue (database check)
$this->assertDatabaseHas('jobs', ['queue' => 'database']); // Adjust based on your driver setup
// In a real integration test, you would then assert that the HTTP response
// was sent immediately, which is implicitly handled by testing the controller flow.
}
}
Notice how we use Bus::dispatch(). When testing interactions with queues in Laravel, understanding the difference between synchronous calls (which wait for processing) and asynchronous dispatching (which merely registers the job) is key. This aligns with best practices discussed across the Laravel documentation.
Method 2: Mocking Queue Interactions for Unit Tests
If your goal is to unit test the logic within a controller or service layer that decides to dispatch a job, rather than testing the queue driver itself, mocking can be extremely effective. By mocking the facade responsible for dispatching (or the job class itself), you isolate the test from the actual blocking nature of the queue setup.
Conclusion: Testing Asynchronous Systems
Testing asynchronous systems requires a shift in perspective—we must test what happens immediately rather than waiting for the entire background process to complete within the scope of the unit or feature test. By focusing assertions on the immediate state changes (like database records being created) and utilizing Laravel’s testing helpers like Bus::dispatch(), developers can accurately validate that their application correctly initiates the asynchronous workflow, regardless of how the underlying queue driver operates during the test execution. Mastering this approach ensures your tests are not only accurate but also reflect the intended behavior of a production system.