If you are a laravel developer who cares about writing well-tested software then you might have seen 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. every time I want to create a new widget I would create a configuration row for it. I used Laravel model events to do so.
class Widget extends Model
{
public static function boot()
{
parent::boot();
static::created(function($model){
dispatch(new CreateWidgetConfiguration($model));
});
}
}
// WidgetsController
public function store(WidgetStoreRequest $request){
Widget::create($request->all());
dispatch(new NewWidgetCreated($model));
}
Every time I create a widget it will fire CreateWidgetConfiguration
event and there is another event called NewWidgetCreated
event that will send an email to account managers.
When It comes to writing tests, and you want to make sure every time you create a new widget it must create a configuration for it but you don’t want to fire a NewWidgetCreated
event because you don’t want to send an email or notification every time we run tests.
So 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 fail because Event::fake() won’t fire the CreateWidgetConfiguration
event.
Let’s take a look at the content of the fake method
public static function fake($eventsToFake = [])
{
static::swap($fake = new EventFake(static::getFacadeRoot(), $eventsToFake));
Model::setEventDispatcher($fake);
}
It says that it will fake events and replace the event dispatcher of the model to be the EventFake
class.
In order to solve this issue and only fake the global Event class without model dispatcher, we have to set Model dispatcher manually after calling the 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. I 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.
If you find any kind of misinformation here, please feel free to let me know and I will update it.
Top comments (1)
Nice article, I found similar solution in this Github Issue.
Another way to do that is specify the event you'd like to fake and then assert its dispatching (for example).