Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

skovmand's avatar

Testing class based events in Laravel 5

Hi.

I have just switched to using the new structure of events in Laravel 5, with Event classes and Event Handler Classes.

For example the event UserCreated is fired whenever a user is created. It takes one argument in its constructor. This is the event:

class UserCreated extends Event {

    use SerializesModels;
    public $user;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }
}

But how can I test that this event is actually fired when the user is created? I need the specific user to create the event, so how this will not work:

Event::shouldReceive('fire')->once()->withArgs([new UserWasCreated()]);

And

Event::shouldReceive('fire')->once()->withAnyArgs();

Is way too unspecific and just catches all events.

Any suggestions?

0 likes
4 replies
MarkRedeman's avatar

As I understand you are currently not writing unit tests, but integration tests (the kind of tests that visit one of your routes and then asserts that everything is OK). These kind of tests are good if you want to know if everything is working, but it is hard to test if specific things are happening (in this case: testing if a specific event was fired). Instead you should try to write some unit tests: tests that only test 1 specific thing. So instead of asserting that the event was fired when you post to some register route, you assert that the method is made when you register a user through some specific method. Here is an example:

<?php

class User {

    private $email;
    private $password;
    private $pendingEvents;

    public static function register($email, $password)
    {
        $this->email = $email;
        $this->password = $password;
        $this->pendingEvents[] = new UserCreated($this);
    }

    public function releaseEvents()
    {
        $events = $this->pendingEvents;

        $this->pendingEvents = [];

        return $events;
    }

    public function email()
    {
        return $this->email;
    }

    public function password()
    {
        return $this->password;
    }
}

class UserTest {

    /**
     * @test
     */
    public function a_user_can_be_registered()
    {
        $password = 'asdf';
        $user = User::register('lorem@ipsum.com', $password);

        // check if the user's properties are correctly set
        $this->assertEquals($user->email(), 'lorem@ipsum.com');
        $this->assertEquals($user->password(), 'asdf');

        // check if the appropriate evetns were made
        $expected = new UserCreated($user);
        $this->assertEquals($user->releaseEvents(), [$expected]);
    }
}

You could also do the following:


class User {

    private $email;
    private $password;

    public static function register($email, $password)
    {
        $this->email = $email;
        $this->password = $password;
        Event::fire(new UserCreated($email, $password));
    }
}

class UserTest {

    /**
     * @test
     */
    public function a_user_can_be_registered()
    {
        Event::shouldReceive('fire')
            ->once()
            ->with([new UserWasCreated('asdf', 'lorem@ipsum.com')]);

        $password = 'asdf';
        $user = User::register('lorem@ipsum.com', $password);

        // check if the user's properties are correctly set
        $this->assertEquals($user->email(), 'lorem@ipsum.com');
        $this->assertEquals($user->password(), 'asdf');
    }
}

But this is a bit harder to test as we have to know the arguments given to the Event::fire() method before we actually create the User object, hence I changed the arguments of the event to take an email and a password.

If anything wasn't clear don't hesitate to ask! Also if you show us some of your (user registration) code I could probably point out what stuff you can change to improve the testability of your code.

1 like
skovmand's avatar

Thanks for your reply. And yes, this is an integration test. I see the idea with using a pendingevents variable, but I would like to avoid that.

I'm still searching for an answer to my question. Before I could write something to the effect of:

eventMock = Event::shouldReceive('fire')->withArgs(['user_created', Mockery::any()])

How do I do this with the class based event system?

This is my best guess, however it doesn't work:

        $eventMock = Event::shouldReceive('fire')
            ->once()
            ->withArgs([\Mockery::type(UserWasCreated::class), \Mockery::any()]);
skovmand's avatar

I found the solution!

The definition of the fire-method in Illuminate\Events\Dispatcher.php is:

public function fire($event, $payload = array(), $halt = false)

Therefore, another \Mockery::any() should be added to the Mockery expectation:

Event::shouldReceive('fire')
            ->once()
            ->withArgs([
                \Mockery::type(UserWasCreated::class),
                \Mockery::any(),
                \Mockery::any()
            ]);
melanholly's avatar

I was searching for something like that but choose to go with the default testing described here https://laravel.com/docs/master/testing#mocking-events

    class ExampleTest extends TestCase
{
    public function testPodcastPurchase()
    {
        $this->expectsEvents(App\Events\PodcastWasPurchased::class);

        $this->doesntExpectEvents(App\Events\PaymentWasDeclined::class);

        // Test purchasing podcast...
    }
}
1 like

Please or to participate in this conversation.