How to Execute Code Before or After Testing in Laravel
In Laravel, when using PHPUnit, we can use the methods setUp() or tearDown(). These methods allow us to execute logic before or after any test method. Additionally, we can use the static methods setUpBeforeClass() or tearDownAfterClass() to run code before or after all tests in a specific testing class.
But how can we run some code before or after executing all the tests? In this article, we will look at two ways to do this: using Laravel events and using PHPUnit events.
The first way — using Laravel events.
In Laravel, we can utilize two events: CommandStarting and CommandFinished. The event CommandStarting is fired just before any Artisan command is executed. The event CommandFinished is fired after an Artisan command has finished executing. These events allow us to perform actions before and after the execution of Artisan commands, including the ‘artisan test’ command. We can create listeners and subscribe to these events in our application. Let’s run these commands to create the listeners.
php artisan make:listener CommandStartingListener
php artisan make:listener CommandFinishedListener
Next, register the listeners in the EventServiceProvider.
<?php
namespace App\Providers;
use App\Listeners\CommandFinishedListener;
use App\Listeners\CommandStartingListener;
use Illuminate\Console\Events\CommandFinished;
use Illuminate\Console\Events\CommandStarting;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
CommandStarting::class => [
CommandStartingListener::class,
],
CommandFinished::class => [
CommandFinishedListener::class,
],
];
}
In the listeners, we should handle the events and determine which command exactly was run. In our case, we are interested in the ‘test’ command. Let’s take a look at our listeners.
This listener is executed before testing.
<?php
namespace App\Listeners;
use Illuminate\Console\Events\CommandStarting;
class CommandStartingListener
{
public function handle(CommandStarting $event)
{
if ($event->command === 'test') {
print "Laravel event before tests" . PHP_EOL;
}
}
}
This listener is executed after testing.
<?php
namespace App\Listeners;
use Illuminate\Console\Events\CommandFinished;
class CommandFinishedListener
{
public function handle(CommandFinished $event)
{
if ($event->command === 'test') {
print "Laravel event after tests" . PHP_EOL;
}
}
}
After creating the listeners, let’s go to the terminal and run the tests:
php artisan test
Now, we can see this output in the terminal.
Both events provide access to the Input and Output objects. The Input object allows us to obtain the CLI options of the command. Let’s output the array of CLI options to the terminal, as shown in this example. We can do this in either of the two listeners.
if ($event->command === 'test') {
dd($event->input->getOptions());
}
Let’s try to run tests with the ‘parallel’ option and look at the output.
php artisan test --parallel
We can see such result in the terminal.
Alternatively, we can check a specific CLI option. For example, we can determine whether the tests are running in parallel, as in this example.
if ($event->command === 'test') {
if ($event->input->getOption('parallel')) {
// Do some stuff when it has the 'parallel' option
}
}
Additionally, we can retrieve a parameter value. In this example, we obtain the count of parallel processes.
$processes = $event->input->getParameterOption('--processes');
The CommandFinished event also provides us with the exit code of the executed command. Using it, we can determine whether the tests have failed, as in this example.
if ($event->exitCode !== 0) {
// Do some stuff when the tests have failed
}
The second way — using PHPUnit events.
This method is applicable not only in Laravel but also in other frameworks, as we will use PHPUnit events. Since version 10.0.0, PHPUnit has an advanced event system with over 60 built-in events. The whole list of the PHPUnit events you can find in the documentation.
In our case, we can use the PHPUnit\Event\Application\Started and PHPUnit\Event\Application\Finished events. The Started event occurs as the PHPUnit CLI application was started. The Finished event occurs as the PHPUnit CLI application has finished.
Now, we need to figure out how to subscribe to these events. Let’s create an Extension directory in the tests directory. Then, create two files for our listener classes TestsStartedSubscriber and TestsFinishedSubscriber.
In PHPUnit, each event has a corresponding listener interface. In our case, the listeners should implement the interfaces PHPUnit\Event\Application\StartedSubscriber and PHPUnit\Event\Application\FinishedSubscriber.
Let’s take a look at our examples.
This listener is executed before testing.
<?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 is executed after testing.
<?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;
}
}
As we can see, our listeners have only one method, notify, which type-hints a relevant event.
After creating our listeners, we need to register them.
For this purpose, PHPUnit requires us to create the class that implements the interface PHPUnit\Runner\Extension\Extension. Let’s create an ExampleExtension class in our Extension directory and put this code there.
<?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(),
);
}
}
As we can see, for listener registration, we should use an instance of the Facade class and its method registerSubscribers().
Now we need to register our ExampleExtension class in the phpunit.xml or phpunit.xml.dist file. We just need to add the ‘extensions’ element to the ‘phpunit’ element, as shown in the following example.
<phpunit ...>
<extensions>
<bootstrap class="Tests\Extension\ExampleExtension"/>
</extensions>
</phpunit>
After creating and registering the listeners, let’s go to the terminal and run the tests.
php artisan test
Since the implementation with Laravel events continues to work, we can see the output of both Laravel events and PHPUnit events together.
The Finished event provides access to the information about memory usage and tests execution duration. We can obtain this information using the methods from the folllowing screenshot.
The Finished event also includes the exit code, allowing us to determine if the tests have failed. We can do this as shown in the following example.
public function notify(Finished $event): void
{
if ($event->shellExitCode() !== 0) {
// Do some stuff when the tests have failed
}
}
Conclusion.
By using Laravel events or PHPUnit events with the ‘artisan test’ command, we can perform pre-execution and post-execution tasks. The events fired before tests can be utilized to prepare fixtures or set up the environment before testing. The events fired after tests can be used for cleanup operations. And of course, all these events can be used for logging or notifications. Choose an event based on your own task.