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

lara8818's avatar

Best way to test Event Listeners

Hey guys,

I feel the title says it all, but regardless... I'm trying to figure out the best way to go about testing Event Listeners.

Using events has made integration testing a breeze since I can focus on only testing the actions that take place in the, well, controller action. However, I still want to ensure that I have complete test coverage against all aspects of my codebase, especially since several of these listeners are responsible for triggering billing systems and such.

My guess is likely just writing standard unit tests in PHPUnit, though I feel like this may look somewhat awkward as PHPUnit is also running my integration tests using the new syntax that was brought in from the Laracast "Integrated" package.

Thoughts?

0 likes
14 replies
a1000frogs's avatar
Level 1

Here's an example of how I'm testing an event listener that sets a user's last sign in and active dates and IP address.

<?php

use App\Events\Users\SignedIn;
use App\Listeners\Users\SignedInListener;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;

class UsersSignedInListenerTest extends TestCase
{
    public function setUp()
    {
        parent::setUp();

        $this->carbon = Mockery::mock(Carbon::class);
        $this->request = Mockery::mock(Request::class);
        $this->user = Mockery::mock(User::class)->makePartial();
    }

    /**
     * Test event listener set users last signin and active timestamps and
     * IP address.
     */
    public function testHandle()
    {
        $time = time();
        $ip = '127.0.0.1';

        $this->carbon->shouldReceive('now')->once()->andReturn($time);
        $this->request->shouldReceive('ip')->once()->andReturn($ip);
        $this->user->shouldReceive('save')->once()->andReturn(true);

        $listener = new SignedInListener($this->carbon, $this->request);

        $listener->handle(new SignedIn($this->user));

        $this->assertSame($time, $this->user->last_signin_at);
        $this->assertSame($time, $this->user->last_active_at);
        $this->assertSame($ip, $this->user->ip);
    }
}
18 likes
lara8818's avatar

This is great @a1000frogs — thank you! I was looking at setting up PHPSpec to handle these kinds of tests but I feel like this may be better suited.

2 likes
james2doyle's avatar

Here is how I tested that an event listener was called without any additional frameworks:

$was_called = false;
$this->app->resolving(function ($object, $app) use (&$was_called) {
    if ($object instanceof MyEventListener) {
        $was_called = true;
    }
});

// do something that invokes the listener

$this->assertTrue($was_called, MyEventListener::class . ' was not called.');

This is very crude as it looks at all the objects being resolved in the application. It is also extremely slow.

4 likes
vrajroham's avatar

Here is how I'm using Mockery to test if the listener was called,

public function testShouldFailIfEventListenerIsNotCalled()
{
    $listener = Mockery::spy(SendOrderDetailsToSupplier::class);
    app()->instance(SendOrderDetailsToSupplier::class, $listener);

    $payload = json_decode(File::get('tests/stubs/accepted-order-eu.json'), true);

    $listener->shouldHaveReceived('handle')
        ->with(Mockery::on(function ($event) use ($payload) {
            return $payload['count'] === $event->payload['count'] &&
                     count($payload['orders']) === count($event->payload['orders']);
    }))
    ->once();
}
4 likes
bait-dept's avatar

@konigbach The problem I am suffering is that the Event fake prevents the listener from being executed and thus doesn't allow me to also test that an underlying part of the listener is actually executing.

bait-dept's avatar

I guess this means I need to go deeper into the tests and also test the Listener itself

jjudge's avatar

@imrodrigoalves That's fine - make the execution a separate test. Keep these things separate - test the listener is subscribed, and test the listener does what it is supposed to do, given state and an event. Two separate tests.

1 like
TuqueroIvantckJernalyn's avatar

We have a Page class that provides the makeRequest method that will emit a request_started event:

const EventEmitter = require('events') const EventEmitter = require('events')

class Page extends EventEmitter { makeRequest(url) { this.emit('request_started', { url }) } } any other part of this codebase can listen out for these events:

page.on('request_started', () => { /* do something here */ })

This is useful functionality, so let's write a test for it:

describe('Page class', () => { it('emits an event when a request is started', () => { const page = new Page()

page.on('request_started', (data) => {
  expect(data.url).toEqual('www.foo.com')
})

page.makeRequest('www.foo.com')

}) }) If we take a look at the test body, think about what happens when the request_started event never fires. Which of the lines below will end up being executed?

it('emits an event when a request is started', () => { const page = new Page()

page.on('request_started', (data) => { expect(data.url).toEqual https://echat.date })

page.makeRequest('www.foo.com') })

HereturbiesWalt's avatar

How can I test an event listeners object? I am using jest, Is it possible with jest?

I know that my event listener works by running it in a main file. But I just wanted to write some tests for good practice.

where should I look at? what are some https://voojio.com/chatroom/omegle examples?

jjudge's avatar

You can test the event listener does its thing by instantiating the listener, then getting it to handle your event.

// Create a listener instance.
$listener = app()->make(YourListener::class); // or just app(YourListener::class)

// Create an event instance.
$event = new YourEvent(model or whatever);

// Bring the two together.
$listener->handle($event);

// Do your assertions
...

You grab the listener from the app container so Laravel gets to inject whatever it needs. After running the event through the handle() method you can assert whatever states that listener should have changed - a status, a job dispatched, a log message written etc.

Testing that the listener is dispatched given the event is created, is a separate test. For that test, just stick to confirming the listsner is dispatched (either sync or async by mocking the queue) and not test what it actually does. Keep those two things separate to keep the tests less complex.

1 like
luciandex's avatar

@consil or another approach bellow.

    /** @test */
    public function event_is_listened()
    {
        Event::fake([
            MyEvent::class
        ]);

       // do your stuff that will trigger the event

        Event::assertListening(
            MyEvent::class,
            MyListener::class
        );
    }
1 like
jekinney's avatar

Suggestions:

  1. If you are testing a function/method that fires an event, test the event was fired there.
  2. Create a separate test class for each listener.

May not be useful for all cases, IE event fired in many place etc.

That said, many times listeners fire a method/function that should be tested already. Point is be careful your not testing 150% percent coverage (random %). I have seen and done that rabbit hole, which you maybe also testing core laravel functions that really don't need testing. But I also get we may have marching orders (SOP's) that require the coverage.

Please or to participate in this conversation.