Testing Deferred Operations in Laravel
Testing deferred operations in Laravel just got easier. The new withoutDefer() and withDefer() test helpers allow you to control when deferred functions execute during your tests.
Basic Usage
Control deferred execution in your tests:
// Without control - deferred operations won't execute immediately
User::create(['name' => 'John']);
$this->assertAgainstSomeDeferredOutcome(); // ❌ Fails
// With control - deferred operations execute immediately
$this->withoutDefer();
User::create(['name' => 'John']);
$this->assertAgainstSomeDeferredOutcome(); // ✅ Passes
Real-World Example
Here's how you might use it in your test suite:
class UserRegistrationTest extends TestCase
{
public function test_welcome_email_is_sent_after_registration()
{
Mail::fake();
// Disable deferred execution
$this->withoutDefer();
$user = User::create([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password'
]);
// Now we can assert immediately
Mail::assertSent(WelcomeEmail::class, function ($mail) use ($user) {
return $mail->hasTo($user->email);
});
}
public function test_user_analytics_are_tracked()
{
Analytics::fake();
$this->withoutDefer();
$user = User::factory()->create();
$user->recordLogin();
Analytics::assertTracked('user_login', function ($event) use ($user) {
return $event['user_id'] === $user->id;
});
// Re-enable deferred execution if needed
$this->withDefer();
}
}
These helpers make it straightforward to test code that relies on deferred execution without waiting for the end of the request lifecycle.
If this guide was helpful to you, subscribe to my daily newsletter and give me a follow on X/Twitter and Bluesky. It helps a lot!