Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[FrameworkBundle][Messenger] Add MessengerAssertionsTrait to test in-memory transports - #65900

Open
nicolas-grekas wants to merge 1 commit into
symfony:8.2from
nicolas-grekas:messenger-test-assertions
Open

[FrameworkBundle][Messenger] Add MessengerAssertionsTrait to test in-memory transports#65900
nicolas-grekas wants to merge 1 commit into
symfony:8.2from
nicolas-grekas:messenger-test-assertions

Conversation

@nicolas-grekas

@nicolas-grekas nicolas-grekas commented Sep 8, 2026

Copy link
Copy Markdown
Member
Q A
Branch? 8.2
Bug fix? no
New feature? yes
Deprecations? no
Issues -
License MIT

This is a proposal, open for discussion. It started from Dariusz Gafka's article Symfony Messenger vs Ecotone: The Real Difference, which describes how Ecotone approaches this. What is proposed here is a free interpretation for Symfony, built on Messenger's own model rather than ported from Ecotone, so it departs from the article where the two models differ. The examples in this description are the article's.

Testing a flow whose second step is asynchronous currently means building a Worker by hand in the test, with an EventDispatcher and a StopWorkerOnMessageLimitListener, and handler failures never surface because the worker turns them into events. FrameworkBundle ships assertion traits for Mailer, Notifier, HttpClient and Console, but nothing for Messenger. This PR adds MessengerAssertionsTrait to KernelTestCase:

$bus->dispatch(new LoanApplication('123', 25000));

$this->assertQueuedMessageCount(1, 'async');
$this->assertQueuedMessageCount(1, 'async', ScoreLoanApplication::class);

$this->consumeQueuedMessages('async');   // runs the queued messages through the real bus
// assert on what the handlers did

The transport must be configured as in-memory:// in the test environment; the helpers fail with an explicit message otherwise.

Public API

FrameworkBundle, Symfony\Bundle\FrameworkBundle\Test\MessengerAssertionsTrait (used by KernelTestCase, so also by WebTestCase):

  • assertQueuedMessageCount(int $count, string $transport, ?string $messageClass = null, string $message = ''): number of envelopes queued on the transport, delayed ones included, optionally only the messages that are instances of the given class. A class name that does not exist is rejected, so a filter cannot silently match nothing after a rename.
  • getQueuedMessages(string $transport): Envelope[]
  • getMessengerTransport(string $transport): InMemoryTransport: the transport service, with a clear failure when the transport is not registered or not in-memory.
  • consumeQueuedMessages(string $transport, ?int $limit = null): int: runs a real Worker on the transport with the application's routable bus and event dispatcher, stops when the transport is empty or after $limit messages, and returns the number of messages handled.

The application's worker listeners run as in production, with one difference that a test needs: a message sent for retry is consumed again without waiting for its delay, so the retries play out inside the call instead of leaving the message parked for retry_strategy.delay seconds. What that gives:

  • a handler that throws once and succeeds on its retry lets the call return normally;
  • a handler that keeps failing exhausts its retries in the same call, and the failure that exhausts them is rethrown, so a broken handler still fails the test;
  • a failure that the strategy will replay is not rethrown, since it is not the outcome of the run;
  • a message the application itself delayed is left in the transport until it is due, and the returned count shows that nothing was handled.

Messenger:

  • StopWorkerOnIdleListener: stops the worker as soon as its receivers return no message (drain and exit).
  • InMemoryTransport implements ListableReceiverInterface (all(), find()) and MessageCountAwareInterface (getMessageCount()), which also makes messenger:show and messenger:stats accept in-memory transports.

Checks

  • ./phpunit src/Symfony/Component/Messenger/Tests and ./phpunit src/Symfony/Bundle/FrameworkBundle/Tests: green (usual missing-server skips). The new functional test app (Tests/Functional/app/Messenger) covers counting, the class filter, the limit, the rethrow and both failure messages of getMessengerTransport().
  • Revert-verified, each behaviour against its own test: without the retry-aware reading, the two retry tests fail; without the gate on willRetry(), a handler that recovers on its retry still aborts the call; without the class check, the unknown-class test passes silently.

The functional test app keeps retry_strategy.delay: 10000 on purpose: the retry tests prove that a ten second delay does not make the test wait.

Documentation

  • Configure in-memory:// in when@test; the four methods and their semantics (delayed messages are counted and listed; envelopes, not messages, are returned).
  • consumeQueuedMessages() runs the real bus and listeners; that retries are replayed without their delay while application delays are honoured; which failure is rethrown and which is not; and how to inspect the failure transport afterwards.
  • StopWorkerOnIdleListener for hand-built workers.

@carsonbot carsonbot added this to the 8.2 milestone Sep 8, 2026
@carsonbot carsonbot changed the title [Messenger][FrameworkBundle] Add MessengerAssertionsTrait to test in-memory transports [FrameworkBundle][Messenger] Add MessengerAssertionsTrait to test in-memory transports Sep 8, 2026
@nicolas-grekas
nicolas-grekas force-pushed the messenger-test-assertions branch from b4e6c52 to f33c36a Compare September 8, 2026 09:36

@alexandre-daubois alexandre-daubois left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes please! Great idea, I remember having to install third party libs to do that.

@wachterjohannes wachterjohannes left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good mechanism overall, the in-memory transport becoming listable and the idle-stop listener are the right building blocks. Replied on alexandre-daubois's retry comment: the fix isn't a one-line gate, the retry/delay interaction needs a design answer first.

@nicolas-grekas
nicolas-grekas force-pushed the messenger-test-assertions branch 2 times, most recently from e59dda9 to ed6b1d0 Compare September 13, 2026 07:15
…memory transports

A functional test that wants to run the messages queued on an in-memory
transport has to build a Worker by hand, with an event dispatcher and a
StopWorkerOnMessageLimitListener. FrameworkBundle ships assertion traits
for Mailer, Notifier, HttpClient and Console but none for Messenger.

Messenger:
- StopWorkerOnIdleListener stops the worker as soon as its receivers
  return no message, so a worker can drain a queue and exit without
  sleeping.
- InMemoryTransport implements ListableReceiverInterface and
  MessageCountAwareInterface: all(), find() and getMessageCount() expose
  the queued envelopes, delayed ones included. The messenger:show and
  messenger:stats commands work with in-memory transports as a result.

FrameworkBundle:
- MessengerAssertionsTrait, composed into KernelTestCase, provides
  assertQueuedMessageCount(), getQueuedMessages(), getMessengerTransport()
  and consumeQueuedMessages(). The last one runs the queued messages
  through the real message bus with the application's event dispatcher,
  so the retry and failure transport listeners behave as in production,
  and rethrows the first handler failure so that it fails the test.
- Listing and consuming queued messages need symfony/messenger 8.2. With
  an older version the trait throws a LogicException that says so, like
  the other Messenger 8.2 features of the bundle.
@nicolas-grekas
nicolas-grekas force-pushed the messenger-test-assertions branch from ed6b1d0 to c7556a3 Compare September 13, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants