Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test that Symfony Messenger events are dispatched in a multi bus scenario using Behat?

Seems difficult and there isn't much documentation around this (I'm using FirendsOfBehat Symfony extension). I want to test whether Transport carries any events by using get() method but I'm not getting any results. It feels like its not routing the correct Bus.

declare(strict_types=1);

namespace App\Features\BehatContext;

class MessengerContext implements Context
{
    /**
     * @var TransportInterface
     */
    private $transport;

    /**
     * MessengerContext constructor.
     *
     * @param TransportInterface $transport ??? Is this ok
     */
    public function __construct(TransportInterface $transport)
    {
        // Symfony\Component\Messenger\Transport\InMemoryTransport
        $this->transport = $transport;
    }

    /**
     * THIS IS WHAT DOESN'T WORK
     * @Given /^(\d+) Events? "([^"]*)" is dispatched$/
     */
    public function eventAEventIsDispatched()
    {
        $eventsDispatched = $this->transport->get();
        Assert::assertTrue(count($eventsDispatched));
    }
}

My packages/messenger.yaml configuration:

framework:
    messenger:
        default_bus: event.bus
        buses:
            command.bus:
                middleware:
                    - validation
            event.bus:
                default_middleware: allow_no_handlers

        transports:
             sync: 'sync://'
             event: 'in-memory:///'

        routing:
             'App\AddMagazine': sync
             'App\MagazineAdded': event
             'App\EventAdapter': event

This is the class that dispatches my events

declare(strict_types=1);

namespace App\Event\Dispatcher;


class SymfonyEventDispatcher implements ApplicationDomainDispatcherInterface
{
    private $messageBus;

    /**
     * SymfonyEventDispatcher constructor.
     *
     * @param MessageBusInterface $sfDispatcher
     */
    public function __construct(MessageBusInterface $eventBus)
    {
        // messageBus is Symfony\Component\Messenger\TraceableMessageBus
        $this->messageBus = $eventBus;
    }

    /**
     * @param EventInterface $event
     *
     * @return EventInterface
     */
    public function dispatch(EventInterface $event): EventInterface
    {
        $eventAdapter = new EventAdapter($event);
        $this->messageBus->dispatch(new Envelope($eventAdapter));

        return $eventAdapter;
    }
}

This my service_test.yaml file which is taken into account when running Behat tests:

services:

    _defaults:
        autowire: true
        autoconfigure: true

    App\Features\BehatContext\:
        resource: '../features/BehatContext/*'

    App\Features\BehatContext\MessengerContext:
        arguments:
            $transport: '@messenger.transport.event'

Checking my logs I can see that the messenger did send the event:

[2019-08-30 14:14:50] messenger.INFO: Sending message App\EventAdapter with Symfony\Component\Messenger\Transport\InMemoryTransport {"message":"[object] (App\EventAdapter: {})","class":"App\EventAdapter","sender":"Symfony\Component\Messenger\Transport\InMemoryTransport"} []

like image 331
db306 Avatar asked Oct 19 '25 04:10

db306


2 Answers

Here's an up to date example for anyone stumbling across this using the when also using the MinkContext

<?php

declare(strict_types=1);

namespace App\Features\Bootstrap;

use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\MinkExtension\Context\MinkContext;
use PHPUnit\Framework\Assert;
use Psr\Container\ContainerInterface;
use Symfony\Component\Messenger\Transport\InMemoryTransport;

/**
 * @author Daniel West <[email protected]>
 */
class MessengerContext implements Context
{
    private ContainerInterface $container;

    /**
     * @BeforeScenario
     */
    public function getContexts(BeforeScenarioScope $scope): void
    {
        /** @var MinkContext $mink */
        $mink = $scope->getEnvironment()->getContext(MinkContext::class);
        $client = $mink->getSession()->getDriver()->getClient();
        $container = $client->getContainer();
        $this->container = $container->has('test.service_container') ? $container->get('test.service_container') : $container;
    }

    /**
     * @Then /(\d+) message(?:s)? should have been sent to the transport named (.*)/
     */
    public function messaesHaveBeenSentToTheTransport(int $count, string $name): void
    {
        /** @var InMemoryTransport $transport */
        $transport = $this->container->get(sprintf('messenger.transport.%s', $name));
        Assert::assertCount($count, $transport->get());
    }
}
like image 84
Daniel West Avatar answered Oct 21 '25 18:10

Daniel West


I think that the problem is that there are two different containers (one for the app and another for Mink isolated driver), and they are not sharing their services state, so it's impossible to get the in-memory enqueued messages.

The first solution that comes to me is renouncing to use the Mink API and start using the BrowserKit API (https://github.com/FriendsOfBehat/SymfonyExtension/pull/82)

The second solution would be using this recently introduced feature for accessing tested application services via the driver's service container: https://github.com/FriendsOfBehat/SymfonyExtension/pull/116

Another common problem comes from making multiple requests during the same scenario, then all the messages stored in the memory will get lost. In that case, I recommend using the doctrine transport.

like image 44
Nove Avatar answered Oct 21 '25 18:10

Nove