Ahmed Ashraf

If you are a laravel developer who cares about writing unit tested software then you probably 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 articles to recommend. 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 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 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 feature test 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 the content of the 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. 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. And if you find any kind of misinformation here, please feel free to let me know and I will update it.

 Happy coding :)

at

21 Feb 2020

Want to be updated with Laravel and other fun related stuff I'm doing. Just Subscribe