I implemented a process from login to a third party system. Because I wanted to be sure to use the right keys and don’t mix up anything between the login subscriber, the message creator and the handler which handles the message from the queue.
In-memory queue for testing
My first idea was to use an in-memory queue for testing so the process just runs as intended, but I didn’t saw a quick way to setup a testing queue without booting the Symfony kernel.
Get the message manually
So I just used my classes and wanted to pickup the message which is added to the queue in one of them. So I came up with the following construct, which we used in the past as well and works great.
<?php declare(strict_types=1); namespace Winkelwagen\Process\Tests\Integration; /** * @coversNothing */ class End2EndTest extends TestCase { public static array $messages; // [...] protected function setUp(): void { parent::setUp(); self::$messages = []; $this->api = $this->createMock(Api::class); $this->setupMessageBus(); } private function setupMessageBus(): void { $this->messageBusMock = (new class implements MessageBusInterface { public function dispatch($message, array $stamps = []): Envelope { End2EndTest::$messages[] = $message; return new Envelope($message); } }); } public function testProcess(): void { $this->api->expects($this->once())->method('createContact')->with('test@example.com'); $guestRegisterEventMock = $this->createMock(GuestCustomerRegisterEvent::class); // add all data to the event, e.g. email $this->prepareEvent($guestRegisterEventMock); $eventHandler = new CreateMessageOnGuestRegister($this->messageBusMock, $this->consentInfoMock); $eventHandler->onGuestRegister($guestRegisterEventMock); $updateCustomerHandler = new GuestRegisterHandler($this->createMock(LoggerInterface::class), $this->api); // and after the eventHandler did things, we can pickup the message easily from our static property $updateCustomerHandler->handle(self::$messages[0]); } }
This way we don’t have to fiddle with the message bus but can just test our own implementations.
Other idea: Test log messages
Last week my colleague implemented log messages for an importer process and we thought about how to test the right order and stuff. In the past one would use withConsecutive
but this feature was deprecated and removed. So if you want to have something else, what about:
private function setupLogger(): void { $this->loggerMock = (new class implements LoggerInterface { private int $i = 1; public function emergency($message, array $context = array()) { $this->log('emergency', $message, $context); } public function alert($message, array $context = array()) { $this->log('alert', $message, $context); } public function critical($message, array $context = array()) { $this->log('critical', $message, $context); } public function error($message, array $context = array()) { $this->log('error', $message, $context); } public function warning($message, array $context = array()) { $this->log('warning', $message, $context); } public function notice($message, array $context = array()) { $this->log('notice', $message, $context); } public function info($message, array $context = array()) { $this->log('info', $message, $context); } public function debug($message, array $context = array()) { $this->log('debug', $message, $context); } public function log($level, $message, array $context = array()) { $message = array_shift(UpdateCustomerWithConsentTest::$logMessages); Assert::assertSame($message, [$level, $message, $context], 'Message number ' . $this->i . ' is not as expected.'); $this->i++; } }); } public function testSomething(): void { self::$logMessages = [ ['debug', 'Handling Message', []], ['warning', 'Message not handled', []], ]; }
So you can define the expected messages and shift them one by one. If the message is wrong PHPUnit will complain.
(The above code for the log messages is untested, so please test it cautiously!)
One thought on “PHPUnit and anonymous classes as “mocks””