Skip to content

Commit d724613

Browse files
committed
Add Messenger assertions trait
Add MessengerAssertionsTrait, reading Symfony's `messenger` profiler data collector, to mirror the existing Mailer/Notifier assertion traits for the previously-uncovered Messenger component: - assertMessageCount(int $expectedCount, ?string $bus = null) - seeMessageDispatched(class-string $messageClass, ?string $bus = null) - dontSeeMessageDispatched(class-string $messageClass, ?string $bus = null) - grabDispatchedMessageClasses(?string $bus = null) Wires the `messenger` collector into DataCollectorName and the grabCollector() conditional return-type map. Adds test-app Messenger config (a default bus plus a sample message, handler, controller action and route) and a unit test covering counts, per-bus filtering, the positive/negative dispatch checks and the no-dispatch case. The profiler collector only exposes cloned snapshots, so assertions are limited to message class, count and bus; grabDispatchedMessageClasses() returns class names (not message objects) by design.
1 parent 7d9317f commit d724613

12 files changed

Lines changed: 262 additions & 2 deletions

File tree

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"symfony/http-foundation": "^5.4 | ^6.4 | ^7.4 | ^8.0",
5353
"symfony/http-kernel": "^5.4 | ^6.4 | ^7.4 | ^8.0",
5454
"symfony/mailer": "^5.4 | ^6.4 | ^7.4 | ^8.0",
55+
"symfony/messenger": "^5.4 | ^6.4 | ^7.4 | ^8.0",
5556
"symfony/mime": "^5.4 | ^6.4 | ^7.4 | ^8.0",
5657
"symfony/notifier": "^5.4 | ^6.4 | ^7.4 | ^8.0",
5758
"symfony/options-resolver": "^5.4 | ^6.4 | ^7.4 | ^8.0",

src/Codeception/Module/Symfony.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Codeception\Module\Symfony\HttpKernelAssertionsTrait;
2323
use Codeception\Module\Symfony\LoggerAssertionsTrait;
2424
use Codeception\Module\Symfony\MailerAssertionsTrait;
25+
use Codeception\Module\Symfony\MessengerAssertionsTrait;
2526
use Codeception\Module\Symfony\MimeAssertionsTrait;
2627
use Codeception\Module\Symfony\NotifierAssertionsTrait;
2728
use Codeception\Module\Symfony\ParameterAssertionsTrait;
@@ -148,6 +149,7 @@ class Symfony extends Framework implements DoctrineProvider, PartedModule
148149
use HttpKernelAssertionsTrait;
149150
use LoggerAssertionsTrait;
150151
use MailerAssertionsTrait;
152+
use MessengerAssertionsTrait;
151153
use MimeAssertionsTrait;
152154
use NotifierAssertionsTrait;
153155
use ParameterAssertionsTrait;

src/Codeception/Module/Symfony/DataCollectorName.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ enum DataCollectorName: string
1818
case TWIG = 'twig';
1919
case SECURITY = 'security';
2020
case MAILER = 'mailer';
21+
case MESSENGER = 'messenger';
2122
case NOTIFIER = 'notifier';
2223
}

src/Codeception/Module/Symfony/HttpKernelAssertionsTrait.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector;
1616
use Symfony\Component\HttpKernel\Profiler\Profile;
1717
use Symfony\Component\Mailer\DataCollector\MessageDataCollector;
18+
use Symfony\Component\Messenger\DataCollector\MessengerDataCollector;
1819
use Symfony\Component\Notifier\DataCollector\NotificationDataCollector;
1920
use Symfony\Component\Translation\DataCollector\TranslationDataCollector;
2021

@@ -40,9 +41,10 @@ abstract protected function getProfile(): ?Profile;
4041
* ($name is DataCollectorName::TWIG ? TwigDataCollector :
4142
* ($name is DataCollectorName::SECURITY ? SecurityDataCollector :
4243
* ($name is DataCollectorName::MAILER ? MessageDataCollector :
44+
* ($name is DataCollectorName::MESSENGER ? MessengerDataCollector :
4345
* ($name is DataCollectorName::NOTIFIER ? NotificationDataCollector :
4446
* DataCollectorInterface
45-
* )))))))))
47+
* ))))))))))
4648
* )
4749
*/
4850
protected function grabCollector(DataCollectorName $name, string $callingFunction = '', ?string $message = null): DataCollectorInterface
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codeception\Module\Symfony;
6+
7+
use Symfony\Component\Messenger\DataCollector\MessengerDataCollector;
8+
use Symfony\Component\VarDumper\Cloner\Data;
9+
10+
use function class_exists;
11+
use function count;
12+
use function is_array;
13+
use function is_string;
14+
use function sprintf;
15+
16+
trait MessengerAssertionsTrait
17+
{
18+
/**
19+
* Asserts that the given number of messages were dispatched through the Messenger buses.
20+
* Optionally pass a bus name (e.g. `messenger.bus.default`) to only count messages dispatched on that bus.
21+
*
22+
* ```php
23+
* <?php
24+
* $I->assertMessageCount(1);
25+
* $I->assertMessageCount(2, 'messenger.bus.default');
26+
* ```
27+
*/
28+
public function assertMessageCount(int $expectedCount, ?string $bus = null, string $message = ''): void
29+
{
30+
$messages = $this->grabMessengerCollector(__FUNCTION__)->getMessages($bus);
31+
32+
$this->assertCount(
33+
$expectedCount,
34+
$messages,
35+
$message !== '' ? $message : sprintf(
36+
'Expected %d message(s) to be dispatched%s, but %d were.',
37+
$expectedCount,
38+
$this->busSuffix($bus),
39+
count($messages),
40+
),
41+
);
42+
}
43+
44+
/**
45+
* Asserts that **no** message of the given class was dispatched through the Messenger buses.
46+
* Optionally restrict the check to a single bus.
47+
*
48+
* ```php
49+
* <?php
50+
* $I->dontSeeMessageDispatched(App\Message\SendWelcomeEmail::class);
51+
* $I->dontSeeMessageDispatched(App\Message\SendWelcomeEmail::class, 'messenger.bus.default');
52+
* ```
53+
*
54+
* @param class-string $messageClass
55+
*/
56+
public function dontSeeMessageDispatched(string $messageClass, ?string $bus = null): void
57+
{
58+
$this->assertNotContains(
59+
$messageClass,
60+
$this->getDispatchedMessageClasses(__FUNCTION__, $bus),
61+
sprintf("The '%s' message was dispatched%s.", $messageClass, $this->busSuffix($bus)),
62+
);
63+
}
64+
65+
/**
66+
* Grabs the fully-qualified **class names** of the messages dispatched through the Messenger buses,
67+
* in dispatch order. Optionally restrict the result to a single bus.
68+
*
69+
* Note: the profiler's messenger collector only stores cloned snapshots, so this returns the message
70+
* **class names**, not the message objects themselves.
71+
*
72+
* ```php
73+
* <?php
74+
* $classes = $I->grabDispatchedMessageClasses();
75+
* $classes = $I->grabDispatchedMessageClasses('messenger.bus.default');
76+
* ```
77+
*
78+
* @return list<class-string>
79+
*/
80+
public function grabDispatchedMessageClasses(?string $bus = null): array
81+
{
82+
return $this->getDispatchedMessageClasses(__FUNCTION__, $bus);
83+
}
84+
85+
/**
86+
* Asserts that at least one message of the given class was dispatched through the Messenger buses.
87+
* Optionally restrict the check to a single bus.
88+
*
89+
* ```php
90+
* <?php
91+
* $I->seeMessageDispatched(App\Message\SendWelcomeEmail::class);
92+
* $I->seeMessageDispatched(App\Message\SendWelcomeEmail::class, 'messenger.bus.default');
93+
* ```
94+
*
95+
* @param class-string $messageClass
96+
*/
97+
public function seeMessageDispatched(string $messageClass, ?string $bus = null): void
98+
{
99+
$this->assertContains(
100+
$messageClass,
101+
$this->getDispatchedMessageClasses(__FUNCTION__, $bus),
102+
sprintf("The '%s' message was not dispatched%s.", $messageClass, $this->busSuffix($bus)),
103+
);
104+
}
105+
106+
/**
107+
* @return list<class-string>
108+
*/
109+
private function getDispatchedMessageClasses(string $callingFunction, ?string $bus): array
110+
{
111+
$messages = $this->grabMessengerCollector($callingFunction)->getMessages($bus);
112+
113+
$classes = [];
114+
foreach ($messages as $entry) {
115+
// The collector clones each entry into a VarDumper Data tree; normalize it back to a plain
116+
// array so we can read the message class name (a ClassStub that getValue() unwraps to a string).
117+
if ($entry instanceof Data) {
118+
$entry = $entry->getValue(true);
119+
}
120+
if (!is_array($entry)) {
121+
continue;
122+
}
123+
124+
$message = $entry['message'] ?? null;
125+
$type = is_array($message) ? ($message['type'] ?? null) : null;
126+
if (is_string($type) && class_exists($type)) {
127+
$classes[] = $type;
128+
}
129+
}
130+
131+
return $classes;
132+
}
133+
134+
private function busSuffix(?string $bus): string
135+
{
136+
return $bus !== null ? sprintf(" on bus '%s'", $bus) : '';
137+
}
138+
139+
protected function grabMessengerCollector(string $callingFunction): MessengerDataCollector
140+
{
141+
return $this->grabCollector(DataCollectorName::MESSENGER, $callingFunction);
142+
}
143+
}

tests/MessengerAssertionsTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests;
6+
7+
use Codeception\Module\Symfony\MessengerAssertionsTrait;
8+
use stdClass;
9+
use Tests\App\Message\TestMessage;
10+
use Tests\Support\CodeceptTestCase;
11+
12+
final class MessengerAssertionsTest extends CodeceptTestCase
13+
{
14+
use MessengerAssertionsTrait;
15+
16+
public function testAssertMessageCount(): void
17+
{
18+
$this->client->request('GET', '/dispatch-message');
19+
20+
$this->assertMessageCount(1);
21+
$this->assertMessageCount(1, 'messenger.bus.default');
22+
$this->assertMessageCount(0, 'non.existent.bus');
23+
}
24+
25+
public function testSeeMessageDispatched(): void
26+
{
27+
$this->client->request('GET', '/dispatch-message');
28+
29+
$this->seeMessageDispatched(TestMessage::class);
30+
$this->seeMessageDispatched(TestMessage::class, 'messenger.bus.default');
31+
}
32+
33+
public function testDontSeeMessageDispatched(): void
34+
{
35+
$this->client->request('GET', '/dispatch-message');
36+
37+
$this->dontSeeMessageDispatched(stdClass::class);
38+
$this->dontSeeMessageDispatched(TestMessage::class, 'non.existent.bus');
39+
}
40+
41+
public function testGrabDispatchedMessageClasses(): void
42+
{
43+
$this->client->request('GET', '/dispatch-message');
44+
45+
$messages = $this->grabDispatchedMessageClasses();
46+
47+
$this->assertSame([TestMessage::class], $messages);
48+
$this->assertSame([TestMessage::class], $this->grabDispatchedMessageClasses('messenger.bus.default'));
49+
$this->assertSame([], $this->grabDispatchedMessageClasses('non.existent.bus'));
50+
}
51+
52+
public function testNoMessagesDispatched(): void
53+
{
54+
$this->client->request('GET', '/');
55+
56+
$this->assertMessageCount(0);
57+
$this->assertSame([], $this->grabDispatchedMessageClasses());
58+
}
59+
}

tests/_app/Controller/AppController.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
use Symfony\Component\HttpFoundation\RedirectResponse;
1717
use Symfony\Component\HttpFoundation\Request;
1818
use Symfony\Component\HttpFoundation\Response;
19+
use Symfony\Component\Messenger\MessageBusInterface;
1920
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
2021
use Symfony\Component\Validator\Constraints\Email as EmailConstraint;
2122
use Symfony\Component\Validator\Constraints\NotBlank;
2223
use Symfony\Contracts\HttpClient\HttpClientInterface;
2324
use Tests\App\Event\TestEvent;
2425
use Tests\App\Mailer\MessageMailer;
2526
use Tests\App\Mailer\RegistrationMailer;
27+
use Tests\App\Message\TestMessage;
2628
use Twig\Environment;
2729

2830
final class AppController extends AbstractController
@@ -52,6 +54,13 @@ public function dispatchEvent(EventDispatcherInterface $dispatcher): Response
5254
return new Response('Event dispatched');
5355
}
5456

57+
public function dispatchMessage(MessageBusInterface $bus): Response
58+
{
59+
$bus->dispatch(new TestMessage('Hello from Messenger'));
60+
61+
return new Response('Message dispatched');
62+
}
63+
5564
public function dispatchNamedEvent(EventDispatcherInterface $dispatcher): Response
5665
{
5766
$dispatcher->dispatch(new TestEvent(), 'named.event');

tests/_app/Message/TestMessage.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\App\Message;
6+
7+
final class TestMessage
8+
{
9+
public function __construct(
10+
private readonly string $content = '',
11+
) {
12+
}
13+
14+
public function getContent(): string
15+
{
16+
return $this->content;
17+
}
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\App\MessageHandler;
6+
7+
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
8+
use Tests\App\Message\TestMessage;
9+
10+
#[AsMessageHandler]
11+
final class TestMessageHandler
12+
{
13+
/** @var list<string> */
14+
public array $handled = [];
15+
16+
public function __invoke(TestMessage $message): void
17+
{
18+
$this->handled[] = $message->getContent();
19+
}
20+
}

tests/_app/TestKernel.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,13 @@ private function configureExtensions(ContainerConfigurator $container): void
5454
'profiler' => $profilerConfig,
5555
'property_info' => ['enabled' => true],
5656
'session' => ['handler_id' => null, 'storage_factory_id' => 'session.storage.factory.mock_file'],
57-
'mailer' => ['dsn' => 'null://null'],
57+
'mailer' => ['dsn' => 'null://null', 'message_bus' => false],
5858
'default_locale' => 'en',
5959
'translator' => ['default_path' => __DIR__ . '/translations', 'fallbacks' => ['es'], 'logging' => true],
6060
'validation' => ['enabled' => true],
6161
'form' => ['enabled' => true],
6262
'notifier' => ['chatter_transports' => ['async' => 'null://null'], 'texter_transports' => ['sms' => 'null://null']],
63+
'messenger' => ['default_bus' => 'messenger.bus.default', 'buses' => ['messenger.bus.default' => []]],
6364
]);
6465

6566
$container->extension('twig', ['default_path' => __DIR__ . '/templates', 'debug' => true]);

0 commit comments

Comments
 (0)