Understand Laravel Event::fake() with Models Events
If you are a laravel developer who cares about writing unit tested software then you might be faced this problem before or at least you are going to face it soon,
In my case It was a task that allows our users to create widgets for their websites, each widget has a configuration with the total number of ads and the total number of recommended articles.
So, I have a Widget
model and WidgetConfiguration
model. so every time I want to create a new widget I want to create a configuration row for it so I used Laravel model events to do it.
class Widget extends Model
{
public static function boot()
{
parent::boot();
static::created(function($model){
dispatch(new CreateWidgetConfiguration($model));
});
}
}
WidgetsController.php
public function store(WidgetStoreRequest $request){
Widget::create($request->all());
dispatch(new NewWidgetCreated($model));
}
So every time I create an widget it will fire CreateWidgetConfiguration
event and there is an another event called NewWidgetCreated
event that will send an emails to account managers.
When It comes to writing tests for it and you want now to write tests to make sure every time you create a new widget it must create a new configuration for it but you don’t want to fire a NewWidgetCreated
event because we don’t want to send an email or notification every time we run tests.
Let’s write the unit tests first
public function test_create_widget_with_configuration()
{ Event::fake(); $user = factory(User::class)->create();
$response = $this->actingAs($user)
->json("POST","/api/widgets",[
'name' => 'My First Widget',
'domain' => 'https://my-first-widget.com',
]); $this->assertDatabaseHas('widgets_configuration',[
'widget_id' => 1
]);}
the previous test will always fails because Event::fake()
won’t fire the CreateWidgetConfiguration
event.
Let’s take a look at content of fake method
public static function fake($eventsToFake = [])
{
static::swap($fake = new EventFake(static::getFacadeRoot(), $eventsToFake));
Model::setEventDispatcher($fake);
}
so the previous block of code says that It will fake Events and replace the event dispatcher of the model to be the EventFake class.
So to solve this issue and only fake the global Event class without model dispatcher we have to set Model dispatcher manually after calling event fake. so the final result will be something like
$initialEvent = Event::getFacadeRoot();
Event::fake();
Model::setEventDispatcher($initialEvent);
and the final test case method is
public function test_create_widget_with_configuration()
{ $initialEvent = Event::getFacadeRoot();
Event::fake();
Model::setEventDispatcher($initialEvent); $user = factory(User::class)->create();
$response = $this->actingAs($user)
->json("POST","/api/widgets",[
'name' => 'My First Widget',
'domain' => 'https://my-first-widget.com',
]);$this->assertDatabaseHas('widgets_configuration',[
'widget_id' => 1
]);}
This is it for now. Hope you at least have got the idea of how event fake works. The main purpose of the article was to let you know what it is and how to deal with it.
And if you find any kind of misinformation here, please feel free to let me know and I will update it.
Happy coding…