Skip to content

Commit 7f0cd5d

Browse files
authored
Add Messenger assertions trait (#241)
1 parent 7d9317f commit 7f0cd5d

13 files changed

Lines changed: 254 additions & 2 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ jobs:
5454
php vendor/bin/codecept run Functional -c framework-tests
5555
5656
if [ "${{ matrix.symfony }}" = "7.4" ]; then
57+
composer remove symfony/messenger --dev --no-interaction
5758
composer require codeception/module-rest --dev
5859
git -C framework-tests checkout -- composer.json
5960
git -C framework-tests apply resetFormatsAfterRequest_issue_test.patch

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: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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_string;
13+
use function sprintf;
14+
15+
trait MessengerAssertionsTrait
16+
{
17+
/**
18+
* Asserts no message of the given class was dispatched (optionally on a single bus).
19+
*
20+
* ```php
21+
* <?php
22+
* $I->dontSeeMessageDispatched(SendWelcomeEmail::class);
23+
* $I->dontSeeMessageDispatched(SendWelcomeEmail::class, 'messenger.bus.default');
24+
* ```
25+
*
26+
* @param class-string $messageClass
27+
*/
28+
public function dontSeeMessageDispatched(string $messageClass, ?string $bus = null): void
29+
{
30+
$this->assertNotContains(
31+
$messageClass,
32+
$this->getDispatchedMessageClasses(__FUNCTION__, $bus),
33+
sprintf("The '%s' message was dispatched%s.", $messageClass, $this->busSuffix($bus)),
34+
);
35+
}
36+
37+
/**
38+
* Returns the dispatched message class names, in dispatch order (optionally for a single bus).
39+
*
40+
* The profiler stores cloned snapshots, so this yields class names, not the message objects.
41+
*
42+
* ```php
43+
* <?php
44+
* $classes = $I->grabDispatchedMessageClasses();
45+
* $classes = $I->grabDispatchedMessageClasses('messenger.bus.default');
46+
* ```
47+
*
48+
* @return list<class-string>
49+
*/
50+
public function grabDispatchedMessageClasses(?string $bus = null): array
51+
{
52+
return $this->getDispatchedMessageClasses(__FUNCTION__, $bus);
53+
}
54+
55+
/**
56+
* Asserts how many messages were dispatched (optionally on a single bus).
57+
*
58+
* ```php
59+
* <?php
60+
* $I->seeDispatchedMessageCount(1);
61+
* $I->seeDispatchedMessageCount(2, 'messenger.bus.default');
62+
* ```
63+
*/
64+
public function seeDispatchedMessageCount(int $expectedCount, ?string $bus = null): void
65+
{
66+
$messages = $this->grabMessengerCollector(__FUNCTION__)->getMessages($bus);
67+
68+
$this->assertCount(
69+
$expectedCount,
70+
$messages,
71+
sprintf(
72+
'Expected %d message(s) to be dispatched%s, but %d were.',
73+
$expectedCount,
74+
$this->busSuffix($bus),
75+
count($messages),
76+
),
77+
);
78+
}
79+
80+
/**
81+
* Asserts at least one message of the given class was dispatched (optionally on a single bus).
82+
*
83+
* ```php
84+
* <?php
85+
* $I->seeMessageDispatched(SendWelcomeEmail::class);
86+
* $I->seeMessageDispatched(SendWelcomeEmail::class, 'messenger.bus.default');
87+
* ```
88+
*
89+
* @param class-string $messageClass
90+
*/
91+
public function seeMessageDispatched(string $messageClass, ?string $bus = null): void
92+
{
93+
$this->assertContains(
94+
$messageClass,
95+
$this->getDispatchedMessageClasses(__FUNCTION__, $bus),
96+
sprintf("The '%s' message was not dispatched%s.", $messageClass, $this->busSuffix($bus)),
97+
);
98+
}
99+
100+
/**
101+
* @return list<class-string>
102+
*/
103+
private function getDispatchedMessageClasses(string $callingFunction, ?string $bus): array
104+
{
105+
$classes = [];
106+
foreach ($this->grabMessengerCollector($callingFunction)->getMessages($bus) as $entry) {
107+
if (!$entry instanceof Data) {
108+
continue;
109+
}
110+
111+
$message = $entry['message'];
112+
$type = $message instanceof Data ? ($message['type'] ?? null) : null;
113+
if ($type instanceof Data) {
114+
$type = $type->getValue();
115+
}
116+
117+
if (is_string($type) && class_exists($type)) {
118+
$classes[] = $type;
119+
}
120+
}
121+
122+
return $classes;
123+
}
124+
125+
private function busSuffix(?string $bus): string
126+
{
127+
return $bus !== null ? sprintf(" on bus '%s'", $bus) : '';
128+
}
129+
130+
protected function grabMessengerCollector(string $callingFunction): MessengerDataCollector
131+
{
132+
return $this->grabCollector(DataCollectorName::MESSENGER, $callingFunction);
133+
}
134+
}

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 testSeeDispatchedMessageCount(): void
17+
{
18+
$this->client->request('GET', '/dispatch-message');
19+
20+
$this->seeDispatchedMessageCount(1);
21+
$this->seeDispatchedMessageCount(1, 'messenger.bus.default');
22+
$this->seeDispatchedMessageCount(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->seeDispatchedMessageCount(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
@@ -66,6 +68,13 @@ public function dispatchOrphanEvent(EventDispatcherInterface $dispatcher): Respo
6668
return new Response('Orphan event dispatched');
6769
}
6870

71+
public function dispatchTestMessage(MessageBusInterface $bus): Response
72+
{
73+
$bus->dispatch(new TestMessage('Hello from Messenger'));
74+
75+
return new Response('Message dispatched');
76+
}
77+
6978
public function form(Request $request, FormFactoryInterface $formFactory, Environment $twig): Response
7079
{
7180
$builder = $formFactory->createNamedBuilder('registration_form', options: ['csrf_protection' => false]);

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+
}

0 commit comments

Comments
 (0)