Event::fake() quietly disables your Eloquent observers too
Event::fake() looks like it targets a specific event. What it actually does is swap out the entire event dispatcher for the rest of the test, which means every listener stops firing — not just the ones related to what you're testing. Since Eloquent's model events (created, updated, deleted) are dispatched through that same system, a broad Event::fake() call silently turns off every observer in the application for the duration of the test, whether or not you meant to touch them.
ON THIS PAGE
What Event::fake() actually swaps out
Calling Event::fake() with no arguments replaces Laravel's real event dispatcher with a fake one that records every dispatched event instead of notifying any listeners:
Event::fake();
event(new OrderShipped($order));
Event::assertDispatched(OrderShipped::class);
This is exactly what you want when the thing under test is "did we dispatch the right event" — it isolates that assertion from whatever side effects the real listeners would have caused. The scope of the swap is the part that's easy to underestimate: it's not "fake this one event," it's "stop every listener in the application from running, for any event, until the test ends."
Eloquent observers run through the same dispatcher
Model events aren't a separate system from application events — created, updated, deleted, and the rest fire through the exact same dispatcher that Event::fake() replaces. An observer registered on a model is, mechanically, just a listener subscribed to those events:
class Order extends Model
{
protected static function booted(): void
{
static::created(function (Order $order) {
AuditLog::record('order_created', $order);
});
}
}
public function test_shipping_notification_is_dispatched(): void
{
Event::fake();
$order = Order::factory()->create(); // AuditLog::record() never runs — silently
$order->ship();
Event::assertDispatched(OrderShipped::class);
$this->assertDatabaseHas('audit_logs', ['action' => 'order_created']);
// fails — not because the audit logic is broken,
// but because Event::fake() turned off the observer that writes it
}
The OrderShipped assertion passes, correctly. The audit log assertion fails, and it's easy to spend real time looking for a bug in the audit logging code before realizing the observer that writes it never ran at all in this test.
Faking selectively instead of everything
Event::fake() accepts an array of specific events to fake, leaving everything else — including model events you didn't name — dispatching normally:
Event::fake([OrderShipped::class]);
$order = Order::factory()->create(); // real event, AuditLog observer still runs
$order->ship(); // OrderShipped is faked, real ShippingNotification listener doesn't run
Event::assertDispatched(OrderShipped::class);
$this->assertDatabaseHas('audit_logs', ['action' => 'order_created']); // passes now
This is almost always the better default once a test file has more than a trivial amount going on — fake exactly the event whose dispatch you're verifying, and leave the rest of the application's event-driven side effects running as they would in production.
The inverse, Event::fakeExcept(), is useful when most events in a test should be faked but one or two specific ones need to keep running for real:
Event::fakeExcept([OrderCreated::class]); // everything else is faked
assertDispatched closures have the same shallow-match trap as assertSent
Same issue as Http::assertSent() — a closure that only checks the event's class, not its actual data, will pass even if the event carried the wrong payload:
Event::assertDispatched(function (OrderShipped $event) {
return true; // technically valid, checks nothing
});
Assert on the specific fields that matter if the payload is what you actually care about:
Event::assertDispatched(function (OrderShipped $event) use ($order) {
return $event->order->id === $order->id
&& $event->order->tracking_number !== null;
});
Queued listeners: faking the event means the listener's logic never runs either
If a listener implements ShouldQueue, faking its event means the listener class itself never executes during the test — there's nothing to assert about what the listener actually did, only that it would have been queued. If you need to test the listener's own logic, call it directly rather than relying on the event to trigger it:
$listener = new SendShippingNotification();
$listener->handle(new OrderShipped($order));
Mail::assertSent(ShippingNotificationMail::class);
This is a separate concern from whether the event fires — testing "does dispatching this event correctly trigger this listener" is a wiring question, and testing "does this listener's logic do the right thing" is a separate one; conflating them under one broad Event::fake() tends to leave one or the other actually unverified.
Checklist before reaching for Event::fake()
- Does this model (or any model touched in this test) have an observer or
booted()event hook that other assertions in this test implicitly depend on? - Would
Event::fake([SpecificEvent::class])cover what you actually need, rather than faking every event in the application? - Do any
assertDispatched()closures check the event's actual payload, or only its class? - If a queued listener's own logic needs verifying, is it being tested directly rather than through a faked event that never lets it run?