Unit test Symfony Messenger
Asked Answered
D

1

23

I've been messing around with implementing AMQP using Symfony Messenger in my latest project. While the code works to my great joy i am unable to write unit tests for methods which dispatches messages.

All my tests involving code that dispatch message result in the following warnings:

Class "Symfony\Component\Messenger\Envelope" is declared "final" and cannot be mocked.

I am unable to find documentation on Symfony's site covering Tests on Symfony Messenger.

How can i test my code with unit tests involving messages?

Class:

namespace App\MessageHandler;

use App\Entity\File;
use App\Message\CheckVideoMessage;
use App\Message\GenerateThumbnailMessage;
use App\Message\GetVideoMetadataMessage;
use App\Message\ProcessFileMessage;
use App\Service\MediaProcessorService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Messenger\MessageBusInterface;

class ProcessFileMessageHandler
{
    /**
     * @var MediaProcessorService
     */
    private $mediaProcessorService;

    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var MessageBusInterface
     */
    private $messageBus;

    public function __construct(
        MediaProcessorService $mediaProcessorService,
        EntityManagerInterface $entityManager,
        LoggerInterface $logger,
        MessageBusInterface $messageBus
    ) {
        $this->mediaProcessorService = $mediaProcessorService;
        $this->entityManager         = $entityManager;
        $this->logger                = $logger;
        $this->messageBus            = $messageBus;
    }

    public function __invoke(ProcessFileMessage $message)
    {
        /** @var File $file */
        $file = $this->entityManager->find(File::class, $message->getFileId());

        if (!$file->getInode()->getMeta()) {
            $this->messageBus->dispatch(new GetVideoMetadataMessage($file->getId()));
        }

        // This event will also dispatch message to generate thumbnails if the video is valid
        $this->messageBus->dispatch(new CheckVideoMessage($file->getId()));

        $this->messageBus->dispatch(new GenerateThumbnailMessage($file->getId()));
    }
}

TestFile:

namespace App\Tests\MessageHandler;

use App\Entity\Inode;
use App\MessageHandler\ProcessFileMessageHandler;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use App\Entity\File;
use App\Message\CheckVideoMessage;
use App\Message\GenerateThumbnailMessage;
use App\Message\GetVideoMetadataMessage;
use App\Message\ProcessFileMessage;
use App\Service\MediaProcessorService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Messenger\MessageBusInterface;

class ProcessFileMessageHandlerTest extends TestCase
{
    /**
     * @var MediaProcessorService|MockObject
     */
    private $mediaProcessorService;

    /**
     * @var EntityManagerInterface|MockObject
     */
    private $entityManager;

    /**
     * @var LoggerInterface|MockObject
     */
    private $logger;

    /**
     * @var MessageBusInterface|MockObject
     */
    private $messageBus;

    /**
     * @var ProcessFileMessageHandler
     */
    private $handler;

    protected function setUp()
    {
        $this->mediaProcessorService = $this->getMockBuilder(MediaProcessorService::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->logger = $this->getMockBuilder(LoggerInterface::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->messageBus = $this->getMockBuilder(MessageBusInterface::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->handler = new ProcessFileMessageHandler(
            $this->mediaProcessorService,
            $this->entityManager,
            $this->logger,
            $this->messageBus
        );
    }

    /**
     * @test
     */
    public function willDispatchAllMessagesOnANewFile()
    {
        $message = new ProcessFileMessage(1);
        $inode   = (new Inode())->setMeta(false);
        $file = (new File())->setId(1)->setInode($inode);

        $this->entityManager->expects($this->once())
            ->method('find')
            ->willReturn($file);

        $this->messageBus->expects(self::exactly(3))
            ->method('dispatch')
            ->withConsecutive(
                self::isInstanceOf(GetVideoMetadataMessage::class),
                self::isInstanceOf(CheckVideoMessage::class),
                self::isInstanceOf(GenerateThumbnailMessage::class)
            );

        $handler = $this->handler;
        $handler($message);
    }
}
Disdain answered 9/1, 2019 at 21:5 Comment(0)
D
63

Problem was resolved by setting a return with an Envelope object.

    $this->messageBus->expects(self::exactly(3))
        ->method('dispatch')
        ->withConsecutive(
            self::isInstanceOf(GetVideoMetadataMessage::class),
            self::isInstanceOf(CheckVideoMessage::class),
            self::isInstanceOf(GenerateThumbnailMessage::class)
        )
        ->willReturn(new Symfony\Component\Messenger\Envelope($message));
Disdain answered 9/1, 2019 at 22:22 Comment(2)
You can accept your own answer as the solution! Thanks for figuring it out. :)Delirious
Ah thanks, i didn't know or figured that out even after six years of StackOverflow :)Disdain

© 2022 - 2024 — McMap. All rights reserved.