From 9d3f6f73707ca65119dfb0ab24cd746e6418d348 Mon Sep 17 00:00:00 2001 From: TavoNiievez Date: Sat, 17 Aug 2024 20:47:53 -0500 Subject: [PATCH] Inherit symfony pre-built assertions --- composer.json | 3 + src/Codeception/Module/Symfony.php | 6 + .../Module/Symfony/BrowserAssertionsTrait.php | 195 +++++++++++++++++- .../Symfony/DomCrawlerAssertionsTrait.php | 176 ++++++++++++++++ .../Module/Symfony/FormAssertionsTrait.php | 25 ++- .../Symfony/HttpClientAssertionsTrait.php | 117 +++++++++++ .../Module/Symfony/MailerAssertionsTrait.php | 68 ++++-- .../Module/Symfony/MimeAssertionsTrait.php | 17 ++ .../Symfony/NotificationAssertionsTrait.php | 91 ++++++++ 9 files changed, 679 insertions(+), 19 deletions(-) create mode 100644 src/Codeception/Module/Symfony/DomCrawlerAssertionsTrait.php create mode 100644 src/Codeception/Module/Symfony/HttpClientAssertionsTrait.php create mode 100644 src/Codeception/Module/Symfony/NotificationAssertionsTrait.php diff --git a/composer.json b/composer.json index 684c23c1..0bdc3bd6 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,7 @@ "type": "library", "keywords": [ "codeception", + "functional testing", "symfony" ], "authors": [ @@ -37,10 +38,12 @@ "symfony/filesystem": "^5.4 | ^6.4 | ^7.0", "symfony/form": "^5.4 | ^6.4 | ^7.0", "symfony/framework-bundle": "^5.4 | ^6.4 | ^7.0", + "symfony/http-client": "^5.4 | ^6.4 | ^7.0", "symfony/http-foundation": "^5.4 | ^6.4 | ^7.0", "symfony/http-kernel": "^5.4 | ^6.4 | ^7.0", "symfony/mailer": "^5.4 | ^6.4 | ^7.0", "symfony/mime": "^5.4 | ^6.4 | ^7.0", + "symfony/notifier": "5.4 | ^6.4 | ^7.0", "symfony/options-resolver": "^5.4 | ^6.4 | ^7.0", "symfony/property-access": "^5.4 | ^6.4 | ^7.0", "symfony/property-info": "^5.4 | ^6.4 | ^7.0", diff --git a/src/Codeception/Module/Symfony.php b/src/Codeception/Module/Symfony.php index c508d5e3..34694898 100644 --- a/src/Codeception/Module/Symfony.php +++ b/src/Codeception/Module/Symfony.php @@ -13,10 +13,13 @@ use Codeception\Module\Symfony\BrowserAssertionsTrait; use Codeception\Module\Symfony\ConsoleAssertionsTrait; use Codeception\Module\Symfony\DoctrineAssertionsTrait; +use Codeception\Module\Symfony\DomCrawlerAssertionsTrait; use Codeception\Module\Symfony\EventsAssertionsTrait; use Codeception\Module\Symfony\FormAssertionsTrait; +use Codeception\Module\Symfony\HttpClientAssertionsTrait; use Codeception\Module\Symfony\MailerAssertionsTrait; use Codeception\Module\Symfony\MimeAssertionsTrait; +use Codeception\Module\Symfony\NotificationAssertionsTrait; use Codeception\Module\Symfony\ParameterAssertionsTrait; use Codeception\Module\Symfony\RouterAssertionsTrait; use Codeception\Module\Symfony\SecurityAssertionsTrait; @@ -135,10 +138,13 @@ class Symfony extends Framework implements DoctrineProvider, PartedModule use BrowserAssertionsTrait; use ConsoleAssertionsTrait; use DoctrineAssertionsTrait; + use DomCrawlerAssertionsTrait; use EventsAssertionsTrait; use FormAssertionsTrait; + use HttpClientAssertionsTrait; use MailerAssertionsTrait; use MimeAssertionsTrait; + use NotificationAssertionsTrait; use ParameterAssertionsTrait; use RouterAssertionsTrait; use SecurityAssertionsTrait; diff --git a/src/Codeception/Module/Symfony/BrowserAssertionsTrait.php b/src/Codeception/Module/Symfony/BrowserAssertionsTrait.php index 9eb56364..cc8bfb54 100644 --- a/src/Codeception/Module/Symfony/BrowserAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/BrowserAssertionsTrait.php @@ -4,11 +4,194 @@ namespace Codeception\Module\Symfony; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\Constraint\LogicalAnd; +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\BrowserKit\Test\Constraint\BrowserCookieValueSame; +use Symfony\Component\BrowserKit\Test\Constraint\BrowserHasCookie; +use Symfony\Component\HttpFoundation\Test\Constraint\RequestAttributeValueSame; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseCookieValueSame; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseFormatSame; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseHasCookie; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseHasHeader; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseHeaderLocationSame; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseHeaderSame; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseIsRedirected; use Symfony\Component\HttpFoundation\Test\Constraint\ResponseIsSuccessful; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseIsUnprocessable; +use Symfony\Component\HttpFoundation\Test\Constraint\ResponseStatusCodeSame; use function sprintf; trait BrowserAssertionsTrait { + /** + * Asserts the given cookie in the test Client is set to the expected value. + */ + public function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', ?string $domain = null, string $message = ''): void + { + $this->assertThatForClient(LogicalAnd::fromConstraints( + new BrowserHasCookie($name, $path, $domain), + new BrowserCookieValueSame($name, $expectedValue, $raw, $path, $domain) + ), $message); + } + + /** + * Asserts that the test Client does have the given cookie set (meaning, the cookie was set by any response in the test). + */ + public function assertBrowserHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void + { + $this->assertThatForClient(new BrowserHasCookie($name, $path, $domain), $message); + } + + /** + * Asserts that the test Client does not have the given cookie set (meaning, the cookie was set by any response in the test). + */ + public function assertBrowserNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void + { + $this->assertThatForClient(new LogicalNot(new BrowserHasCookie($name, $path, $domain)), $message); + } + + /** + * Asserts the given request attribute is set to the expected value. + */ + public function assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = ''): void + { + $this->assertThat($this->getClient()->getRequest(), new RequestAttributeValueSame($name, $expectedValue), $message); + } + + /** + * Asserts the given cookie is present and set to the expected value. + */ + public function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', ?string $domain = null, string $message = ''): void + { + $this->assertThatForResponse(LogicalAnd::fromConstraints( + new ResponseHasCookie($name, $path, $domain), + new ResponseCookieValueSame($name, $expectedValue, $path, $domain) + ), $message); + } + + /** + * Asserts the response format returned by the `Response::getFormat()` method is the same as the expected value. + */ + public function assertResponseFormatSame(?string $expectedFormat, string $message = ''): void + { + $this->assertThatForResponse(new ResponseFormatSame($this->getClient()->getRequest(), $expectedFormat), $message); + } + + /** + * Asserts the given cookie is present in the response (optionally checking for a specific cookie path or domain). + */ + public function assertResponseHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void + { + $this->assertThatForResponse(new ResponseHasCookie($name, $path, $domain), $message); + } + + /** + * Asserts the given header is available on the response, e.g. assertResponseHasHeader('content-type');. + */ + public function assertResponseHasHeader(string $headerName, string $message = ''): void + { + $this->assertThatForResponse(new ResponseHasHeader($headerName), $message); + } + + /** + * Asserts the given header does not contain the expected value on the response, + * e.g. assertResponseHeaderNotSame('content-type', 'application/octet-stream');. + */ + public function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = ''): void + { + $this->assertThatForResponse(new LogicalNot(new ResponseHeaderSame($headerName, $expectedValue)), $message); + } + + /** + * Asserts the given header does contain the expected value on the response, + * e.g. assertResponseHeaderSame('content-type', 'application/octet-stream');. + */ + public function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = ''): void + { + $this->assertThatForResponse(new ResponseHeaderSame($headerName, $expectedValue), $message); + } + + /** + * Asserts that the response was successful (HTTP status is 2xx). + */ + public function assertResponseIsSuccessful(string $message = '', bool $verbose = true): void + { + $this->assertThatForResponse(new ResponseIsSuccessful($verbose), $message); + } + + /** + * Asserts the response is unprocessable (HTTP status is 422) + */ + public function assertResponseIsUnprocessable(string $message = '', bool $verbose = true): void + { + $this->assertThatForResponse(new ResponseIsUnprocessable($verbose), $message); + } + + /** + * Asserts the given cookie is not present in the response (optionally checking for a specific cookie path or domain). + */ + public function assertResponseNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void + { + $this->assertThatForResponse(new LogicalNot(new ResponseHasCookie($name, $path, $domain)), $message); + } + + /** + * Asserts the given header is not available on the response, e.g. assertResponseNotHasHeader('content-type');. + */ + public function assertResponseNotHasHeader(string $headerName, string $message = ''): void + { + $this->assertThatForResponse(new LogicalNot(new ResponseHasHeader($headerName)), $message); + } + + /** + * Asserts the response is a redirect response (optionally, you can check the target location and status code). + * The excepted location can be either an absolute or a relative path. + */ + public function assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', bool $verbose = true): void + { + $constraint = new ResponseIsRedirected($verbose); + if ($expectedLocation) { + if (class_exists(ResponseHeaderLocationSame::class)) { + $locationConstraint = new ResponseHeaderLocationSame($this->getClient()->getRequest(), $expectedLocation); + } else { + $locationConstraint = new ResponseHeaderSame('Location', $expectedLocation); + } + + $constraint = LogicalAnd::fromConstraints($constraint, $locationConstraint); + } + if ($expectedCode) { + $constraint = LogicalAnd::fromConstraints($constraint, new ResponseStatusCodeSame($expectedCode)); + } + + $this->assertThatForResponse($constraint, $message); + } + + /** + * Asserts a specific HTTP status code. + */ + public function assertResponseStatusCodeSame(int $expectedCode, string $message = '', bool $verbose = true): void + { + $this->assertThatForResponse(new ResponseStatusCodeSame($expectedCode, $verbose), $message); + } + + /** + * Asserts the request matches the given route and optionally route parameters. + */ + public function assertRouteSame(string $expectedRoute, array $parameters = [], string $message = ''): void + { + $constraint = new RequestAttributeValueSame('_route', $expectedRoute); + $constraints = []; + foreach ($parameters as $key => $value) { + $constraints[] = new RequestAttributeValueSame($key, $value); + } + if ($constraints) { + $constraint = LogicalAnd::fromConstraints($constraint, ...$constraints); + } + + $this->assertThat($this->getClient()->getRequest(), $constraint, $message); + } + /** * Reboot client's kernel. * Can be used to manually reboot kernel when 'rebootable_client' => false @@ -50,7 +233,7 @@ public function seePageIsAvailable(?string $url = null): void $this->seeInCurrentUrl($url); } - $this->assertThat($this->getClient()->getResponse(), new ResponseIsSuccessful()); + $this->assertResponseIsSuccessful(); } /** @@ -104,4 +287,14 @@ public function submitSymfonyForm(string $name, array $fields): void $this->submitForm($selector, $params, $button); } + + protected function assertThatForClient(Constraint $constraint, string $message = ''): void + { + $this->assertThat($this->getClient(), $constraint, $message); + } + + protected function assertThatForResponse(Constraint $constraint, string $message = ''): void + { + $this->assertThat($this->getClient()->getResponse(), $constraint, $message); + } } diff --git a/src/Codeception/Module/Symfony/DomCrawlerAssertionsTrait.php b/src/Codeception/Module/Symfony/DomCrawlerAssertionsTrait.php new file mode 100644 index 00000000..0d5bca59 --- /dev/null +++ b/src/Codeception/Module/Symfony/DomCrawlerAssertionsTrait.php @@ -0,0 +1,176 @@ +assertThat($this->getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new CrawlerAnySelectorTextContains($selector, $text) + ), $message); + } + + /** + * Asserts that any element matching the given selector does not contain the expected text. + */ + public function assertAnySelectorTextNotContains(string $selector, string $text, string $message = ''): void + { + $this->assertThat($this->getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new LogicalNot(new CrawlerAnySelectorTextContains($selector, $text)) + ), $message); + } + + /** + * Asserts that any element matching the given selector does equal the expected text. + */ + public function assertAnySelectorTextSame(string $selector, string $text, string $message = ''): void + { + $this->assertThat($this->getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new CrawlerAnySelectorTextSame($selector, $text) + ), $message); + } + + /** + * Asserts that the checkbox with the given name is checked. + */ + public function assertCheckboxChecked(string $fieldName, string $message = ''): void + { + $this->assertThat( + $this->getCrawler(), + new CrawlerSelectorExists("input[name=\"$fieldName\"]:checked"), + $message + ); + } + + /** + * Asserts that the checkbox with the given name is not checked. + */ + public function assertCheckboxNotChecked(string $fieldName, string $message = ''): void + { + $this->assertThat( + $this->getCrawler(), + new LogicalNot(new CrawlerSelectorExists("input[name=\"$fieldName\"]:checked")), + $message + ); + } + + /** + * Asserts that value of the form input with the given name does not equal the expected value. + */ + public function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = ''): void + { + $this->assertThat($this->getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists("input[name=\"$fieldName\"]"), + new LogicalNot(new CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue)) + ), $message); + } + + /** + * Asserts that value of the form input with the given name does equal the expected value. + */ + public function assertInputValueSame(string $fieldName, string $expectedValue, string $message = ''): void + { + $this->assertThat($this->getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists("input[name=\"$fieldName\"]"), + new CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue) + ), $message); + } + + /** + * Asserts that the `` element contains the given title. + */ + public function assertPageTitleContains(string $expectedTitle, string $message = ''): void + { + $this->assertSelectorTextContains('title', $expectedTitle, $message); + } + + /** + * Asserts that the `<title>` element is equal to the given title. + */ + public function assertPageTitleSame(string $expectedTitle, string $message = ''): void + { + $this->assertSelectorTextSame('title', $expectedTitle, $message); + } + + /** + * Asserts that the expected number of selector elements are in the response. + */ + public function assertSelectorCount(int $expectedCount, string $selector, string $message = ''): void + { + $this->assertThat($this->getCrawler(), new CrawlerSelectorCount($expectedCount, $selector), $message); + } + + /** + * Asserts that the given selector does match at least one element in the response. + */ + public function assertSelectorExists(string $selector, string $message = ''): void + { + $this->assertThat($this->getCrawler(), new CrawlerSelectorExists($selector), $message); + } + + /** + * Asserts that the given selector does not match at least one element in the response. + */ + public function assertSelectorNotExists(string $selector, string $message = ''): void + { + $this->assertThat($this->getCrawler(), new LogicalNot(new CrawlerSelectorExists($selector)), $message); + } + + /** + * Asserts that the first element matching the given selector does contain the expected text. + */ + public function assertSelectorTextContains(string $selector, string $text, string $message = ''): void + { + $this->assertThat($this->getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new CrawlerSelectorTextContains($selector, $text) + ), $message); + } + + /** + * Asserts that the first element matching the given selector does not contain the expected text. + */ + public function assertSelectorTextNotContains(string $selector, string $text, string $message = ''): void + { + $this->assertThat($this->getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new LogicalNot(new CrawlerSelectorTextContains($selector, $text)) + ), $message); + } + + /** + * Asserts that the contents of the first element matching the given selector does equal the expected text. + */ + public function assertSelectorTextSame(string $selector, string $text, string $message = ''): void + { + $this->assertThat($this->getCrawler(), LogicalAnd::fromConstraints( + new CrawlerSelectorExists($selector), + new CrawlerSelectorTextSame($selector, $text) + ), $message); + } + + protected function getCrawler(): Crawler + { + return $this->client->getCrawler(); + } +} diff --git a/src/Codeception/Module/Symfony/FormAssertionsTrait.php b/src/Codeception/Module/Symfony/FormAssertionsTrait.php index 930969c1..0c8736f0 100644 --- a/src/Codeception/Module/Symfony/FormAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/FormAssertionsTrait.php @@ -12,6 +12,29 @@ trait FormAssertionsTrait { + /** + * Asserts that value of the field of the first form matching the given selector does equal the expected value. + */ + public function assertFormValue(string $formSelector, string $fieldName, string $value, string $message = ''): void + { + $node = $this->getCrawler()->filter($formSelector); + $this->assertNotEmpty($node, sprintf('Form "%s" not found.', $formSelector)); + $values = $node->form()->getValues(); + $this->assertArrayHasKey($fieldName, $values, $message ?: sprintf('Field "%s" not found in form "%s".', $fieldName, $formSelector)); + $this->assertSame($value, $values[$fieldName]); + } + + /** + * Asserts that value of the field of the first form matching the given selector does equal the expected value. + */ + public function assertNoFormValue(string $formSelector, string $fieldName, string $message = ''): void + { + $node = $this->getCrawler()->filter($formSelector); + $this->assertNotEmpty($node, sprintf('Form "%s" not found.', $formSelector)); + $values = $node->form()->getValues(); + $this->assertArrayNotHasKey($fieldName, $values, $message ?: sprintf('Field "%s" has a value in form "%s".', $fieldName, $formSelector)); + } + /** * Verifies that there are no errors bound to the submitted form. * @@ -42,8 +65,6 @@ public function dontSeeFormErrors(): void * $I->seeFormErrorMessage('username'); * $I->seeFormErrorMessage('username', 'Username is empty'); * ``` - * - * @param string|null $message */ public function seeFormErrorMessage(string $field, ?string $message = null): void { diff --git a/src/Codeception/Module/Symfony/HttpClientAssertionsTrait.php b/src/Codeception/Module/Symfony/HttpClientAssertionsTrait.php new file mode 100644 index 00000000..9ac3a6e4 --- /dev/null +++ b/src/Codeception/Module/Symfony/HttpClientAssertionsTrait.php @@ -0,0 +1,117 @@ +<?php + +declare(strict_types=1); + +namespace Codeception\Module\Symfony; + +use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; +use function array_key_exists; +use function is_string; + +trait HttpClientAssertionsTrait +{ + /** + * Asserts that the given URL has been called using, if specified, the given method body and headers. + * By default, it will check on the HttpClient, but you can also pass a specific HttpClient ID. (It will succeed if the request has been called multiple times.) + */ + public function assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array|null $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client'): void + { + $httpClientCollector = $this->grabHttpClientCollector(__FUNCTION__); + $expectedRequestHasBeenFound = false; + + if (!array_key_exists($httpClientId, $httpClientCollector->getClients())) { + $this->fail(sprintf('HttpClient "%s" is not registered.', $httpClientId)); + } + + foreach ($httpClientCollector->getClients()[$httpClientId]['traces'] as $trace) { + if (($expectedUrl !== $trace['info']['url'] && $expectedUrl !== $trace['url']) + || $expectedMethod !== $trace['method'] + ) { + continue; + } + + if (null !== $expectedBody) { + $actualBody = null; + + if (null !== $trace['options']['body'] && null === $trace['options']['json']) { + $actualBody = is_string($trace['options']['body']) ? $trace['options']['body'] : $trace['options']['body']->getValue(true); + } + + if (null === $trace['options']['body'] && null !== $trace['options']['json']) { + $actualBody = $trace['options']['json']->getValue(true); + } + + if (!$actualBody) { + continue; + } + + if ($expectedBody === $actualBody) { + $expectedRequestHasBeenFound = true; + + if (!$expectedHeaders) { + break; + } + } + } + + if ($expectedHeaders) { + $actualHeaders = $trace['options']['headers'] ?? []; + + foreach ($actualHeaders as $headerKey => $actualHeader) { + if (array_key_exists($headerKey, $expectedHeaders) + && $expectedHeaders[$headerKey] === $actualHeader->getValue(true) + ) { + $expectedRequestHasBeenFound = true; + break 2; + } + } + } + + $expectedRequestHasBeenFound = true; + break; + } + + $this->assertTrue($expectedRequestHasBeenFound, 'The expected request has not been called: "' . $expectedMethod . '" - "' . $expectedUrl . '"'); + } + + /** + * Asserts that the given number of requests has been made on the HttpClient. + * By default, it will check on the HttpClient, but you can also pass a specific HttpClient ID. + */ + public function assertHttpClientRequestCount(int $count, string $httpClientId = 'http_client'): void + { + $httpClientCollector = $this->grabHttpClientCollector(__FUNCTION__); + + $this->assertCount($count, $httpClientCollector->getClients()[$httpClientId]['traces']); + } + + /** + * Asserts that the given URL has not been called using GET or the specified method. + * By default, it will check on the HttpClient, but a HttpClient id can be specified. + */ + public function assertNotHttpClientRequest(string $unexpectedUrl, string $expectedMethod = 'GET', string $httpClientId = 'http_client'): void + { + $httpClientCollector = $this->grabHttpClientCollector(__FUNCTION__); + $unexpectedUrlHasBeenFound = false; + + if (!array_key_exists($httpClientId, $httpClientCollector->getClients())) { + $this->fail(sprintf('HttpClient "%s" is not registered.', $httpClientId)); + } + + foreach ($httpClientCollector->getClients()[$httpClientId]['traces'] as $trace) { + if (($unexpectedUrl === $trace['info']['url'] || $unexpectedUrl === $trace['url']) + && $expectedMethod === $trace['method'] + ) { + $unexpectedUrlHasBeenFound = true; + break; + } + } + + $this->assertFalse($unexpectedUrlHasBeenFound, sprintf('Unexpected URL called: "%s" - "%s"', $expectedMethod, $unexpectedUrl)); + } + + protected function grabHttpClientCollector(string $function): HttpClientDataCollector + { + return $this->grabCollector('http_client', $function); + } +} diff --git a/src/Codeception/Module/Symfony/MailerAssertionsTrait.php b/src/Codeception/Module/Symfony/MailerAssertionsTrait.php index f8bb9772..df2fd0f1 100644 --- a/src/Codeception/Module/Symfony/MailerAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/MailerAssertionsTrait.php @@ -4,6 +4,8 @@ namespace Codeception\Module\Symfony; +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\Mailer\Event\MessageEvent; use Symfony\Component\Mailer\Event\MessageEvents; use Symfony\Component\Mailer\EventListener\MessageLoggerListener; use Symfony\Component\Mailer\Test\Constraint as MailerConstraint; @@ -12,30 +14,47 @@ trait MailerAssertionsTrait { /** - * Checks that no email was sent. - * The check is based on `\Symfony\Component\Mailer\EventListener\MessageLoggerListener`, which means: - * If your app performs an HTTP redirect, you need to suppress it using [stopFollowingRedirects()](https://codeception.com/docs/modules/Symfony#stopFollowingRedirects) first; otherwise this check will *always* pass. + * Asserts that the expected number of emails was sent. */ - public function dontSeeEmailIsSent(): void + public function assertEmailCount(int $count, ?string $transport = null, string $message = ''): void { - $this->assertThat($this->getMessageMailerEvents(), new MailerConstraint\EmailCount(0)); + $this->assertThat($this->getMessageMailerEvents(), new MailerConstraint\EmailCount($count, $transport), $message); } /** - * Checks if the given number of emails was sent (default `$expectedCount`: 1). + * Asserts that the given mailer event is not queued. + * Use `getMailerEvent(int $index = 0, ?string $transport = null)` to retrieve a mailer event by index. + */ + public function assertEmailIsNotQueued(MessageEvent $event, string $message = ''): void + { + $this->assertThat($event, new LogicalNot(new MailerConstraint\EmailIsQueued()), $message); + } + + /** + * Asserts that the given mailer event is queued. + * Use `getMailerEvent(int $index = 0, ?string $transport = null)` to retrieve a mailer event by index. + */ + public function assertEmailIsQueued(MessageEvent $event, string $message = ''): void + { + $this->assertThat($event, new MailerConstraint\EmailIsQueued(), $message); + } + + /** + * Asserts that the expected number of emails was queued (e.g. using the Messenger component). + */ + public function assertQueuedEmailCount(int $count, ?string $transport = null, string $message = ''): void + { + $this->assertThat($this->getMessageMailerEvents(), new MailerConstraint\EmailCount($count, $transport, true), $message); + } + + /** + * Checks that no email was sent. * The check is based on `\Symfony\Component\Mailer\EventListener\MessageLoggerListener`, which means: - * If your app performs an HTTP redirect after sending the email, you need to suppress it using [stopFollowingRedirects()](https://codeception.com/docs/modules/Symfony#stopFollowingRedirects) first. - * - * ```php - * <?php - * $I->seeEmailIsSent(2); - * ``` - * - * @param int $expectedCount The expected number of emails sent + * If your app performs an HTTP redirect, you need to suppress it using [stopFollowingRedirects()](https://codeception.com/docs/modules/Symfony#stopFollowingRedirects) first; otherwise this check will *always* pass. */ - public function seeEmailIsSent(int $expectedCount = 1): void + public function dontSeeEmailIsSent(): void { - $this->assertThat($this->getMessageMailerEvents(), new MailerConstraint\EmailCount($expectedCount)); + $this->assertThat($this->getMessageMailerEvents(), new MailerConstraint\EmailCount(0)); } /** @@ -78,6 +97,23 @@ public function grabSentEmails(): array return $this->getMessageMailerEvents()->getMessages(); } + /** + * Checks if the given number of emails was sent (default `$expectedCount`: 1). + * The check is based on `\Symfony\Component\Mailer\EventListener\MessageLoggerListener`, which means: + * If your app performs an HTTP redirect after sending the email, you need to suppress it using [stopFollowingRedirects()](https://codeception.com/docs/modules/Symfony#stopFollowingRedirects) first. + * + * ```php + * <?php + * $I->seeEmailIsSent(2); + * ``` + * + * @param int $expectedCount The expected number of emails sent + */ + public function seeEmailIsSent(int $expectedCount = 1): void + { + $this->assertThat($this->getMessageMailerEvents(), new MailerConstraint\EmailCount($expectedCount)); + } + protected function getMessageMailerEvents(): MessageEvents { if ($messageLogger = $this->getService('mailer.message_logger_listener')) { diff --git a/src/Codeception/Module/Symfony/MimeAssertionsTrait.php b/src/Codeception/Module/Symfony/MimeAssertionsTrait.php index d20ea306..a55b13ba 100644 --- a/src/Codeception/Module/Symfony/MimeAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/MimeAssertionsTrait.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\Constraint\LogicalNot; use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\RawMessage; use Symfony\Component\Mime\Test\Constraint as MimeConstraint; trait MimeAssertionsTrait @@ -133,6 +134,22 @@ public function assertEmailNotHasHeader(string $headerName, ?Email $email = null $this->assertThat($email, new LogicalNot(new MimeConstraint\EmailHasHeader($headerName))); } + /** + * Asserts that the subject of the given email does contain the expected subject. + */ + public function assertEmailSubjectContains(RawMessage $email, string $expectedValue, string $message = ''): void + { + $this->assertThat($email, new MimeConstraint\EmailSubjectContains($expectedValue), $message); + } + + /** + * Asserts that the subject of the given email does not contain the expected subject. + */ + public function assertEmailSubjectNotContains(RawMessage $email, string $expectedValue, string $message = ''): void + { + $this->assertThat($email, new LogicalNot(new MimeConstraint\EmailSubjectContains($expectedValue)), $message); + } + /** * Verify the text body of an email contains a `$text`. * If the Email object is not specified, the last email sent is used instead. diff --git a/src/Codeception/Module/Symfony/NotificationAssertionsTrait.php b/src/Codeception/Module/Symfony/NotificationAssertionsTrait.php new file mode 100644 index 00000000..c8b0f74c --- /dev/null +++ b/src/Codeception/Module/Symfony/NotificationAssertionsTrait.php @@ -0,0 +1,91 @@ +<?php + +declare(strict_types=1); + +namespace Codeception\Module\Symfony; + +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\Notifier\Event\MessageEvent; +use Symfony\Component\Notifier\Event\NotificationEvents; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Test\Constraint\NotificationCount; +use Symfony\Component\Notifier\Test\Constraint\NotificationIsQueued; +use Symfony\Component\Notifier\Test\Constraint\NotificationSubjectContains; +use Symfony\Component\Notifier\Test\Constraint\NotificationTransportIsEqual; + +trait NotificationAssertionsTrait +{ + /** + * Asserts that the given number of notifications has been created (in total or for the given transport). + */ + public function assertNotificationCount(int $count, ?string $transportName = null, string $message = ''): void + { + $this->assertThat($this->getNotificationEvents(), new NotificationCount($count, $transportName), $message); + } + + /** + * Asserts that the given notification is not queued. + */ + public function assertNotificationIsNotQueued(MessageEvent $event, string $message = ''): void + { + $this->assertThat($event, new LogicalNot(new NotificationIsQueued()), $message); + } + + /** + * Asserts that the given notification is queued. + */ + public function assertNotificationIsQueued(MessageEvent $event, string $message = ''): void + { + $this->assertThat($event, new NotificationIsQueued(), $message); + } + + /** + * Asserts that the given text is included in the subject of the given notification. + */ + public function assertNotificationSubjectContains(MessageInterface $notification, string $text, string $message = ''): void + { + $this->assertThat($notification, new NotificationSubjectContains($text), $message); + } + + /** + * Asserts that the given text is not included in the subject of the given notification. + */ + public function assertNotificationSubjectNotContains(MessageInterface $notification, string $text, string $message = ''): void + { + $this->assertThat($notification, new LogicalNot(new NotificationSubjectContains($text)), $message); + } + + /** + * Asserts that the name of the transport for the given notification is the same as the given text. + */ + public function assertNotificationTransportIsEqual(MessageInterface $notification, ?string $transportName = null, string $message = ''): void + { + $this->assertThat($notification, new NotificationTransportIsEqual($transportName), $message); + } + + /** + * Asserts that the name of the transport for the given notification is not the same as the given text. + */ + public function assertNotificationTransportIsNotEqual(MessageInterface $notification, ?string $transportName = null, string $message = ''): void + { + $this->assertThat($notification, new LogicalNot(new NotificationTransportIsEqual($transportName)), $message); + } + + /** + * Asserts that the given number of notifications are queued (in total or for the given transport). + */ + public function assertQueuedNotificationCount(int $count, ?string $transportName = null, string $message = ''): void + { + $this->assertThat($this->getNotificationEvents(), new NotificationCount($count, $transportName, true), $message); + } + + protected function getNotificationEvents(): NotificationEvents + { + $notificationLogger = $this->getService('notifier.notification_logger_listener'); + if ($notificationLogger) { + return $notificationLogger->getEvents(); + } + + $this->fail('A client must have Notifier enabled to make notifications assertions. Did you forget to require symfony/notifier?'); + } +} <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml'> <head> <title>pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy