Registering your PHPUnit test as an event subscriber for testing events
--
I love leveraging events in my application architecture. Drupal uses the EventDispatcher component from Symfony, which implements the Mediator and Observer design patterns. This allows for business logic to be extensible without making systems entirely coupled. The originating system dispatches an event and allows any other system to react to that event or modify data associated with that event. The originating system can then perform other interactions after its observers have processed the event.
Most of the time, we write tests for our subscribers to an event, as we subscribe to existing events in our custom modules. But, what if our custom code is introducing new events? A common pattern that I have seen is creating a test module that defines an event subscriber for the event that then pushes some data into the application state, which the test then reads. I believe there is a better way to reduce the amount of boilerplate code!
Did you know you can register your test class as an event subscriber and handle subscribing to dispatched events?
Note! This will not work with Unit tests, as there isn’t a bootstrapped Drupal kernel, which requires a database connection. This is intended for Kernel tests, which have a minimally bootstrapped Drupal kernel.
The base test class for Kernel tests, \Drupal\KernelTests\KernelTestBase
, implements \Drupal\Core\DependencyInjection\ServiceProviderInterface
. Drupal's dependency injection implementation uses this to allow classes to state that they register services to the service container. The ::bootKernel
method adds the class as a service provider
// Add this test class as a service provider.
$GLOBALS['conf']['container_service_providers']['test'] = $this;
Drupal reads from the container_service_providers
array and verifies they implement ServiceProviderInterface
and call their register
method. The following is an example of the register
implementation in a test class. We create a new service definition and tag it as an event subscriber, and then set the service as our current class.
// Register our class with the service container as an event subscriber.
public function register(ContainerBuilder $container): void {
parent::register($container);
$container
->register('testing.config_save_subscriber', self::class)
->addTag('event_subscriber')…