From 572a74f0a21ac84665e2c31117be8c1e66fcde36 Mon Sep 17 00:00:00 2001 From: d-mitrofanov-v Date: Sun, 9 Nov 2025 17:33:24 +0200 Subject: [PATCH 1/3] add notifier assertions trait --- src/Codeception/Module/Symfony.php | 17 ++ .../Module/Symfony/DataCollectorName.php | 1 + .../Symfony/NotifierAssertionsTrait.php | 253 ++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 src/Codeception/Module/Symfony/NotifierAssertionsTrait.php diff --git a/src/Codeception/Module/Symfony.php b/src/Codeception/Module/Symfony.php index 0f98cf1..776681e 100644 --- a/src/Codeception/Module/Symfony.php +++ b/src/Codeception/Module/Symfony.php @@ -21,6 +21,7 @@ use Codeception\Module\Symfony\LoggerAssertionsTrait; use Codeception\Module\Symfony\MailerAssertionsTrait; use Codeception\Module\Symfony\MimeAssertionsTrait; +use Codeception\Module\Symfony\NotifierAssertionsTrait; use Codeception\Module\Symfony\ParameterAssertionsTrait; use Codeception\Module\Symfony\RouterAssertionsTrait; use Codeception\Module\Symfony\SecurityAssertionsTrait; @@ -52,6 +53,7 @@ use Symfony\Component\HttpKernel\Profiler\Profile; use Symfony\Component\HttpKernel\Profiler\Profiler; use Symfony\Component\Mailer\DataCollector\MessageDataCollector; +use Symfony\Component\Notifier\DataCollector\NotificationDataCollector; use Symfony\Component\Translation\DataCollector\TranslationDataCollector; use Symfony\Component\VarDumper\Cloner\Data; @@ -156,6 +158,7 @@ class Symfony extends Framework implements DoctrineProvider, PartedModule use LoggerAssertionsTrait; use MailerAssertionsTrait; use MimeAssertionsTrait; + use NotifierAssertionsTrait; use ParameterAssertionsTrait; use RouterAssertionsTrait; use SecurityAssertionsTrait; @@ -412,6 +415,7 @@ protected function getProfile(): ?Profile * ($collector is DataCollectorName::TWIG ? TwigDataCollector : * ($collector is DataCollectorName::SECURITY ? SecurityDataCollector : * ($collector is DataCollectorName::MAILER ? MessageDataCollector : + * ($collector is DataCollectorName::NOTIFIER ? NotificationDataCollector : * DataCollectorInterface * )))))))) * ) @@ -465,6 +469,13 @@ protected function debugResponse(mixed $url): void } } + if ($profile->hasCollector(DataCollectorName::NOTIFIER->value)) { + $notifierCollector = $profile->getCollector(DataCollectorName::NOTIFIER->value); + if ($notifierCollector instanceof NotificationDataCollector) { + $this->debugNotifierData($notifierCollector); + } + } + if ($profile->hasCollector(DataCollectorName::TIME->value)) { $timeCollector = $profile->getCollector(DataCollectorName::TIME->value); if ($timeCollector instanceof TimeDataCollector) { @@ -543,6 +554,12 @@ private function debugMailerData(MessageDataCollector $messageCollector): void $this->debugSection('Emails', sprintf('%d sent', $count)); } + private function debugNotifierData(NotificationDataCollector $notificationCollector): void + { + $count = count($notificationCollector->getEvents()->getMessages()); + $this->debugSection('Notifications', sprintf('%d sent', $count)); + } + private function debugTimeData(TimeDataCollector $timeCollector): void { $this->debugSection('Time', sprintf('%.2f ms', $timeCollector->getDuration())); diff --git a/src/Codeception/Module/Symfony/DataCollectorName.php b/src/Codeception/Module/Symfony/DataCollectorName.php index efa8687..e6eb392 100644 --- a/src/Codeception/Module/Symfony/DataCollectorName.php +++ b/src/Codeception/Module/Symfony/DataCollectorName.php @@ -18,4 +18,5 @@ enum DataCollectorName: string case TWIG = 'twig'; case SECURITY = 'security'; case MAILER = 'mailer'; + case NOTIFIER = 'notifier'; } diff --git a/src/Codeception/Module/Symfony/NotifierAssertionsTrait.php b/src/Codeception/Module/Symfony/NotifierAssertionsTrait.php new file mode 100644 index 0000000..8124f0a --- /dev/null +++ b/src/Codeception/Module/Symfony/NotifierAssertionsTrait.php @@ -0,0 +1,253 @@ +assertNotificationCount(2, 'smtp'); + * ``` + */ + public function assertNotificationCount(int $count, ?string $transportName = null, string $message = ''): void + { + $this->assertThat($this->getNotificationEvents(), new NotifierConstraint\NotificationCount($count, $transportName), $message); + } + + /** + * Asserts that the given notifier event is not queued. + * Use `getNotifierEvent(int $index = 0, ?string $transportName = null)` to retrieve a notifier event by index. + * + * ```php + * getNotifierEvent(); + * $I->asserNotificationIsNotQueued($event); + * ``` + */ + public function assertNotificationIsNotQueued(MessageEvent $event, string $message = ''): void + { + $this->assertThat($event, new LogicalNot(new NotifierConstraint\NotificationIsQueued()), $message); + } + + /** + * Asserts that the given notifier event is queued. + * Use `getNotifierEvent(int $index = 0, ?string $transportName = null)` to retrieve a notifier event by index. + * + * ```php + * getNotifierEvent(); + * $I->assertNotificationlIsQueued($event); + * ``` + */ + public function assertNotificationIsQueued(MessageEvent $event, string $message = ''): void + { + $this->assertThat($event, new NotifierConstraint\NotificationIsQueued(), $message); + } + + /** + * Asserts that the given notification contains given subject. + * Use `getNotifierMessage(int $index = 0, ?string $transportName = null)` to retrieve a notification by index. + * + * ```php + * getNotifierMessage(); + * $I->assertNotificationSubjectContains($notification, 'Subject'); + * ``` + */ + public function assertNotificationSubjectContains(MessageInterface $notification, string $text, string $message = ''): void + { + $bbep = $this->assertThat($notification, new NotifierConstraint\NotificationSubjectContains($text), $message); + $test = $notification; + } + + /** + * Asserts that the given notification does not contain given subject. + * Use `getNotifierMessage(int $index = 0, ?string $transportName = null)` to retrieve a notification by index. + * + * ```php + * getNotifierMessage(); + * $I->assertNotificationSubjectNotContains($notification, 'Subject'); + * ``` + */ + public function assertNotificationSubjectNotContains(MessageInterface $notification, string $text, string $message = ''): void + { + $this->assertThat($notification, new LogicalNot(new NotifierConstraint\NotificationSubjectContains($text)), $message); + } + + /** + * Asserts that the given notification uses given transport. + * Use `getNotifierMessage(int $index = 0, ?string $transportName = null)` to retrieve a notification by index. + * + * ```php + * getNotifierMessage(); + * $I->assertNotificationTransportIsEqual($notification, 'chat'); + * ``` + */ + public function assertNotificationTransportIsEqual(MessageInterface $notification, ?string $transportName = null, string $message = ''): void + { + $this->assertThat($notification, new NotifierConstraint\NotificationTransportIsEqual($transportName), $message); + } + + /** + * Asserts that the given notification does not use given transport. + * Use `getNotifierMessage(int $index = 0, ?string $transportName = null)` to retrieve a notification by index. + * + * ```php + * getNotifierMessage(); + * $I->assertNotificationTransportIsNotEqual($notification, 'transport'); + * ``` + */ + public function assertNotificationTransportIsNotEqual(MessageInterface $notification, ?string $transportName = null, string $message = ''): void + { + $this->assertThat($notification, new LogicalNot(new NotifierConstraint\NotificationTransportIsEqual($transportName)), $message); + } + + /** + * Asserts that the expected number of notifications was queued (e.g. using the Notifier component). + * + * ```php + * assertQueuedNotificationCount(1, 'smtp'); + * ``` + */ + public function assertQueuedNotificationCount(int $count, ?string $transportName = null, string $message = ''): void + { + $this->assertThat($this->getNotificationEvents(), new NotifierConstraint\NotificationCount($count, $transportName, true), $message); + } + + /** + * Checks that no notification was sent. + * The check is based on `\Symfony\Component\Notifier\EventListener\NotificationLoggerListener`, which means: + * If your app performs an HTTP redirect, you need to suppress it using [stopFollowingRedirects()](#stopFollowingRedirects) first; otherwise this check will *always* pass. + * + * ```php + * dontSeeNotificationIsSent(); + * ``` + */ + public function dontSeeNotificationIsSent(): void + { + $this->assertThat($this->getNotificationEvents(), new NotifierConstraint\NotificationCount(0)); + } + + /** + * Returns the last sent notification. + * The check is based on `\Symfony\Component\Notifier\EventListener\NotificationLoggerListener`, which means: + * If your app performs an HTTP redirect after sending the notification, you need to suppress it using [stopFollowingRedirects()](#stopFollowingRedirects) first. + * See also: [grabSentNotifications()](https://codeception.com/docs/modules/Symfony#grabSentNotifications) + * + * ```php + * grabLastSentNotification(); + * $I->assertSame('Subject', $message->getSubject()); + * ``` + */ + public function grabLastSentNotification(): ?MessageInterface + { + $notification = $this->getNotifierMessages(); + $lastNotification = end($notification); + + return $lastNotification ?: null; + } + + + /** + * Returns an array of all sent notifications. + * The check is based on `\Symfony\Component\Notifier\EventListener\NotificationLoggerListener`, which means: + * If your app performs an HTTP redirect after sending the notification, you need to suppress it using [stopFollowingRedirects()](#stopFollowingRedirects) first. + * See also: [grabLastSentNotification()](https://codeception.com/docs/modules/Symfony#grabLastSentNotification) + * + * ```php + * grabSentNotifications(); + * ``` + * + * @return MessageEvent[] + */ + public function grabSentNotifications(): array + { + return $this->getNotifierMessages(); + } + + /** + * Checks if the given number of notifications was sent (default `$expectedCount`: 1). + * The check is based on `\Symfony\Component\Notifier\EventListener\NotificationLoggerListener`, which means: + * If your app performs an HTTP redirect after sending the notification, you need to suppress it using [stopFollowingRedirects()](#stopFollowingRedirects) first. + * + * ```php + * seeNotificatoinIsSent(2); + * ``` + * + * @param int $expectedCount The expected number of notifications sent + */ + public function seeNotificationIsSent(int $expectedCount = 1): void + { + $this->assertThat($this->getNotificationEvents(), new NotifierConstraint\NotificationCount($expectedCount)); + } + + public function getNotifierEvents(?string $transportName = null): array + { + return $this->getNotificationEvents()->getEvents($transportName); + } + + /** + * Returns the notifier event at the specified index. + * + * ```php + * getNotifierEvent(); + * ``` + */ + public function getNotifierEvent(int $index = 0, ?string $transportName = null): ?MessageEvent + { + return $this->getNotifierEvents($transportName)[$index] ?? null; + } + + public function getNotifierMessages(?string $transportName = null): array + { + return $this->getNotificationEvents()->getMessages($transportName); + } + + /** + * Returns the notifier message at the specified index. + * + * ```php + * getNotifierMessage(); + * ``` + */ + public function getNotifierMessage(int $index = 0, ?string $transportName = null): ?MessageInterface + { + return $this->getNotifierMessages($transportName)[$index] ?? null; + } + + protected function getNotificationEvents(): NotificationEvents + { + $notifier = $this->getService('notifier.notification_logger_listener'); + if ($notifier instanceof NotificationLoggerListener) { + return $notifier->getEvents(); + } + $notifier = $this->getService('notifier.logger_notification_listener'); + if ($notifier instanceof NotificationLoggerListener) { + return $notifier->getEvents(); + } + Assert::fail("Notifications can't be tested without Symfony Notifier service."); + } +} From 06825cbc5a93604eeff4a86b2a42e9339641d57f Mon Sep 17 00:00:00 2001 From: "mitrofanov.dm" Date: Tue, 11 Nov 2025 09:11:27 +0200 Subject: [PATCH 2/3] add check for symfony version for notifier assertions --- .../Module/Symfony/NotifierAssertionsTrait.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Codeception/Module/Symfony/NotifierAssertionsTrait.php b/src/Codeception/Module/Symfony/NotifierAssertionsTrait.php index 8124f0a..31402d1 100644 --- a/src/Codeception/Module/Symfony/NotifierAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/NotifierAssertionsTrait.php @@ -11,6 +11,7 @@ use Symfony\Component\Notifier\EventListener\NotificationLoggerListener; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Test\Constraint as NotifierConstraint; +use Symfony\Component\HttpKernel\Kernel; trait NotifierAssertionsTrait { @@ -69,8 +70,7 @@ public function assertNotificationIsQueued(MessageEvent $event, string $message */ public function assertNotificationSubjectContains(MessageInterface $notification, string $text, string $message = ''): void { - $bbep = $this->assertThat($notification, new NotifierConstraint\NotificationSubjectContains($text), $message); - $test = $notification; + $this->assertThat($notification, new NotifierConstraint\NotificationSubjectContains($text), $message); } /** @@ -240,6 +240,10 @@ public function getNotifierMessage(int $index = 0, ?string $transportName = null protected function getNotificationEvents(): NotificationEvents { + if (version_compare(Kernel::VERSION, '6.2', '<')) { + Assert::fail('Notifier assertions require Symfony 6.2 or higher.'); + } + $notifier = $this->getService('notifier.notification_logger_listener'); if ($notifier instanceof NotificationLoggerListener) { return $notifier->getEvents(); From 15dcb6447e66a1570419c81e2a3a79caaae9c27c Mon Sep 17 00:00:00 2001 From: "mitrofanov.dm" Date: Tue, 11 Nov 2025 10:09:27 +0200 Subject: [PATCH 3/3] fix missing parenthesis --- src/Codeception/Module/Symfony.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Codeception/Module/Symfony.php b/src/Codeception/Module/Symfony.php index 776681e..ff11a36 100644 --- a/src/Codeception/Module/Symfony.php +++ b/src/Codeception/Module/Symfony.php @@ -417,7 +417,7 @@ protected function getProfile(): ?Profile * ($collector is DataCollectorName::MAILER ? MessageDataCollector : * ($collector is DataCollectorName::NOTIFIER ? NotificationDataCollector : * DataCollectorInterface - * )))))))) + * ))))))))) * ) * * @throws AssertionFailedError