Ambrosius Topor

Notiz
(2019-01-19)

PHPUnit: Clean up listeners

Situation

I've had the situation that I needed to test for Events to be dispatched:

public function testSomethingThatDispatchesAnEvent()
{
    $eventDispatcher->addListener('command.execution.completed', $listener);

    // do assertion ..

    // remove listener
    $eventDispatcher->removeListener('command.execution.completed', $listener);
}

The implementation was working, but when running the test intentionally failing, the output was showing me that the removal of the listeners was not working correctly. Actually the output was bloated, because it was showing the error for the subsequent tests, too, which was far from being useful. After having a closer look, the reason became clear: The test did quit before having a chance to run the clean up code.

Solution

The solution I came up with was to register the added listeners to a list, and iterate over this after the test ran, removing every listener.

Implementation

Register

Initialize an array to hold information about the registered listeners:

public function setUp()
{
    $this->listenersToRemove = [];
}

Clean up

Remove listeners after each test method run:

public function tearDown()
{
    // Remove listeners
    foreach ($this->listenersToRemove as $listener) {
        $this->eventDispatcher->removeListener($listener['eventName'], $listener['callable']);
    }
}

Add the listener to the array in the test method:

public function testSomethingThatDispatchesAnEvent()
{
    // setup code ..

    $this->listenersToRemove[] = [
        'eventName' => 'command.execution.completed',
        'callable' => $listener,
    ];
    $this->eventDispatcher->addListener('command.execution.completed', $listener);

    // assertions ..

    // no need to remove listener!
}

Further optimization

Have a custom method to add the listeners and also register them to the list:

private function addEventDispatcherListener(string $eventName, callable $listener)
{
    $this->listenersToRemove[] = [
        'eventName' => $eventName,
        'callable' => $listener,
    ];
    $this->eventDispatcher->addListener($eventName, $listener);
}

This will make the first implementation much cleaner:

public function testSomethingThatDispatchesAnEvent()
{
    // setup code ..

    $this->addEventDispatcherListener('command.execution.completed', $listener);

    // assertions ..
}