DEV Community

Cover image for Understand Laravel Event::fake() with (Model Events)
Ahmed Ashraf
Ahmed Ashraf

Posted on

Understand Laravel Event::fake() with (Model Events)

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.

Latest comments (1)

Collapse
 
fakeheal profile image
Ivanka Todorova

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).

Event::fake([Registered::class]);
// make requests, call models, etc
Event::assertDispatched(Registered::class);
Enter fullscreen mode Exit fullscreen mode