Testing if event listeners are attached to events in Laravel

September 9, 2020

I really like the event system in Laravel. It’s a really nice way to perform side-effects in applications by having a completely separated class where I can test in isolation. I usually prefer to write a unit test for event listener than to write a full feature test which touches all parts of the application where I might have to mock half of the services. To me, this kind of separation ends up creating reliable tests which don’t break when they shouldn’t, and this is really key for having a maintainable test suite.

One thing I always wanted to assert in my listeners unit tests though, and have never found a good way, is a check that verifies if my event listener is attached to the correct event(s). A test like test may seem unnecessary, but your future self and any future developers refactoring this code will thank you.

<?php

class SendShipmentNotificationTest extends TestCase
{
    public function test_is_attached_to_event()
    {
        $this->assertListenerIsAttachedToEvent(
            App\Listeners\SendShipmentNotification::class,
            App\Events\OrderShippedEvent::class
        );
    }
}

Really simple, right? if it breaks, the message was given to the developer making the refactor. Let’s take a look on how the implementation of assertListenerIsAttachedToEvent looks like:

<?php

use ReflectionFunction;
use Illuminate\Events\Dispatcher;

public function assertListenerIsAttachedToEvent($listener, $event)
{
    $dispatcher = app(Dispatcher::class);

    foreach ($dispatcher->getListeners(is_object($event) ? get_class($event) : $event) as $listenerClosure) {
        $reflection = new ReflectionFunction($listenerClosure);
        $listenerClass = $reflection->getStaticVariables()['listener'];

        if ($listenerClass === $listener) {
            $this->assertTrue(true);

            return;
        }
    }

    $this->assertTrue(false, sprintf('Event %s does not have the %s listener attached to it', $event, $listener));
}

This wasn’t big enough to make a package and I don’t see this fitting anywhere exactly in Laravel for a contribution, so I thought I would at least share this here.


That’s all. I hope you found this article helpful. Be sure to hit me up on Twitter at @luisdalmolin if you have any feedback.

UPDATE:

I’ve changed my mind, and I’ve opened a PR against Laravel. So, since Laravel v8.34.0, there’s a new Event::assertListening assertion you can use. You can check out usage in the Laravel docs.