Tests Symfony RabbitMQBundle Producers with PHPUnit
Hi y'all!
In a Symfony project, we are using RabbitMQBundle to setup the broker, produce and consume messages. But, we needed to tests variants of messages a consumer might end up handling.
With Symfony and PHPUnit it is straightforward to test a service and to replace communication with external tools such as RabbitMQ.
Decorate a producer to trace your messages
In a use case you would want to test if a message is well send, you would want to decorate your publisher to stop the propagation and keep messages for future assertions.
I will need to inherite from YourSpecialProducer
because my producers are autowired as services.
<?php
declare(strict_types=1);
namespace App\Tests\Application;
use App\RabbitMQ\Producer\YourSpecialProducer;
class NullPublisherProducer extends YourSpecialProducer
{
use TraceableProducerTrait;
public function __construct()
{
$this->initializeTrace();
}
}
<?php
declare(strict_types=1);
namespace App\Tests\Application;
trait TraceableProducerTrait
{
/** @var array */
private $trace;
private function initializeTrace(): void
{
$this->trace = [];
}
public function getTrace(): array
{
return $this->trace;
}
public function publish($msgBody, $routingKey = '', $additionalProperties = array(), array $headers = null): void
{
$this->trace[] = [
'body' => $msgBody,
'routing_key' => $routingKey,
'properties' => $additionalProperties,
'headers' => $headers,
];
}
}
# config/services_test.yaml
services:
nullable_special_producer:
class: App\Tests\Application\NullPublisherProducer
decorates: App\RabbitMQ\Producer\YourSpecialProducer
Handle trace in PHPUnit
You can now use this new method to assert a number of message sent from a Producer or to get the content from them to be more specific.
<?php
declare(strict_types=1);
namespace App\Tests\RabbitMQ\Consumer;
use App\RabbitMQ\Consumer\MyMessageConsumer;
use App\RabbitMQ\Producer\YourSpecialProducer;
use PhpAmqpLib\Message\AMQPMessage;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* a message in my-queue contains
* Routing Key your-routing-key
*
* Properties
* priority: 0
* delivery_mode: 2
* headers:
* content_type: application/json
*/
class MyMessageConsumerTest extends KernelTestCase
{
/**
* @var ContainerInterface gets the special container that allows fetching private services
*/
protected static $container;
public function setUp(): void
{
self::bootKernel();
}
public function testMessageShouldBeParsedForExternalClient(): void
{
$matchJSON = self::$container->getParameter('kernel.project_dir'). '/data/file.json';
$this->sendMessage(file_get_contents($matchJSON));
$trace = self::$container->get(YourSpecialProducer::class)->getTrace();
self::assertCount(1, $trace);
}
private function sendMessage(string $json): void
{
$SUT = self::$container->get(MyMessageConsumer::class);
$msg = new AMQPMessage($json, [
'priority' => 0,
'delivery_mode' => 2,
]);
$SUT->consume($msg);
}
}
This was an example from a real use case adapted and simplified to the bare mimimum. Don't hesitate to enhance your test cases.