Since PHPUnit 10, the framework introduced a powerful event system with more than 60 built-in events. These events allow us to hook into different stages of the test lifecycle, making it possible to execute custom logic before or after the tests run. While this example uses Laravel, the same approach works in any framework because we are working directly with PHPUnit’s event system.
For demonstration, we will use two events:
PHPUnit\Event\Application\Started
— triggered when the PHPUnit CLI application starts.PHPUnit\Event\Application\Finished
— triggered when the PHPUnit CLI application finishes.
By subscribing to these events, we can run custom code before and after the test suite.
Inside your tests/Extension
directory, create two listener classes.
tests/Extension/TestsStartedSubscriber.php
namespace Tests\Extension;
use PHPUnit\Event\Application\Started;
use PHPUnit\Event\Application\StartedSubscriber;
class TestsStartedSubscriber implements StartedSubscriber
{
public function notify(Started $event): void
{
print "PHPUnit event before tests" . PHP_EOL;
}
}
This listener will execute before any tests run.
tests/Extension/TestsFinishedSubscriber.php
namespace Tests\Extension;
use PHPUnit\Event\Application\Finished;
use PHPUnit\Event\Application\FinishedSubscriber;
class TestsFinishedSubscriber implements FinishedSubscriber
{
public function notify(Finished $event): void
{
print "PHPUnit event after tests" . PHP_EOL;
}
}
This listener will execute after all tests have finished.
Each subscriber only needs a notify
method, type-hinting the corresponding event.
To let PHPUnit know about our subscribers, we need to register them via an extension class. Create an ExampleExtension
class in the same directory:
tests/Extension/ExampleExtension.php
namespace Tests\Extension;
use PHPUnit\Runner\Extension\Extension;
use PHPUnit\Runner\Extension\Facade;
use PHPUnit\Runner\Extension\ParameterCollection;
use PHPUnit\TextUI\Configuration\Configuration;
class ExampleExtension implements Extension
{
public function bootstrap(
Configuration $configuration,
Facade $facade,
ParameterCollection $parameters,
): void {
$facade->registerSubscribers(
new TestsStartedSubscriber(),
new TestsFinishedSubscriber(),
);
}
}
Here, we use PHPUnit’s Facade
to register our custom subscribers.
Finally, register the extension in your phpunit.xml
(or phpunit.xml.dist
):
<phpunit>
<extensions>
<bootstrap class="Tests\Extension\ExampleExtension"/>
</extensions>
</phpunit>
Running the tests
Now, when you run your tests:
php artisan test
You should see the custom messages printed before and after the test run.
You can use this pattern for example to verify certain things before allowing the tests to run. For example, you can use it to check the existence of a .env.testing
file:
namespace Tests;
use PHPUnit\Event\Application\Started;
use PHPUnit\Event\Application\StartedSubscriber;
class TestsStartedSubscriber implements StartedSubscriber
{
public function notify(Started $event): void
{
if (!file_exists('.env.testing')) {
echo('Copy the .env.testing.example file to .env.testing before running the tests.' . PHP_EOL);
exit(1);
}
}
}
With this in place, whenever you try to run the whole test suite, a subset of it or even a single test using e.g. PhpStorm, the check will happen and abort the testing if the file isn't present.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.