Back to blog posts

Tests Symfony RabbitMQBundle Producers with PHPUnit

Cover Image for Tests Symfony RabbitMQBundle Producers with PHPUnit
Dylan Ballandras
Dylan Ballandras

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.