diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 85b0d49d..c95a2224 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,11 +6,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: [8.1, 8.2, 8.3, 8.4] - symfony: ["5.4.*", "6.4.*", "7.2.*"] - exclude: - - php: 8.1 - symfony: "7.2.*" + php: [8.2, 8.3, 8.4] + symfony: ["5.4.*", "6.4.*", "6.4wApi", "7.2.*"] steps: - name: Checkout code @@ -25,11 +22,26 @@ jobs: coverage: none - name: Set Symfony version reference - run: echo "SF_REF=${MATRIX_SYMFONY%.*}" >> $GITHUB_ENV env: MATRIX_SYMFONY: ${{ matrix.symfony }} + run: | + if [[ "$MATRIX_SYMFONY" == *'*' ]]; then + echo "SF_REF=${MATRIX_SYMFONY%.*}" >> "$GITHUB_ENV" + else + echo "SF_REF=$MATRIX_SYMFONY" >> "$GITHUB_ENV" + fi + + - name: Set Composer Symfony constraint + env: + MATRIX_SYMFONY: ${{ matrix.symfony }} + run: | + if [[ "$MATRIX_SYMFONY" == "6.4wApi" ]]; then + echo "COMP_SYMFONY=6.4.*" >> "$GITHUB_ENV" + else + echo "COMP_SYMFONY=$MATRIX_SYMFONY" >> "$GITHUB_ENV" + fi - - name: Checkout Symfony ${{ env.SF_REF }} Sample + - name: Checkout Symfony ${{ env.SF_REF }} sample uses: actions/checkout@v4 with: repository: Codeception/symfony-module-tests @@ -51,17 +63,24 @@ jobs: run: composer require --dev --no-update "phpunit/phpunit=^10.0" - name: Install dependencies + env: + MATRIX_SYMFONY: ${{ matrix.symfony }} run: | - composer require symfony/finder=${{ matrix.symfony }} --no-update - composer require symfony/yaml=${{ matrix.symfony }} --no-update - composer require symfony/console=${{ matrix.symfony }} --no-update - composer require symfony/event-dispatcher=${{ matrix.symfony }} --no-update - composer require symfony/css-selector=${{ matrix.symfony }} --no-update - composer require symfony/dom-crawler=${{ matrix.symfony }} --no-update - composer require symfony/browser-kit=${{ matrix.symfony }} --no-update + composer require symfony/finder=${{ env.COMP_SYMFONY }} --no-update + composer require symfony/yaml=${{ env.COMP_SYMFONY }} --no-update + composer require symfony/console=${{ env.COMP_SYMFONY }} --no-update + composer require symfony/event-dispatcher=${{ env.COMP_SYMFONY }} --no-update + composer require symfony/css-selector=${{ env.COMP_SYMFONY }} --no-update + composer require symfony/dom-crawler=${{ env.COMP_SYMFONY }} --no-update + composer require symfony/browser-kit=${{ env.COMP_SYMFONY }} --no-update composer require vlucas/phpdotenv --no-update composer require codeception/module-asserts="3.*" --no-update composer require codeception/module-doctrine="3.*" --no-update + + if [[ "$MATRIX_SYMFONY" == "6.4wApi" ]]; then + composer require codeception/module-rest="3.*" --no-update + fi + composer update --prefer-dist --no-progress --no-dev - name: Validate Composer files @@ -84,6 +103,11 @@ jobs: php bin/console doctrine:fixtures:load --quiet working-directory: framework-tests + - name: Generate JWT keypair + if: ${{ matrix.symfony == '6.4wApi' }} + run: php bin/console lexik:jwt:generate-keypair --skip-if-exists + working-directory: framework-tests + - name: Run tests run: | php vendor/bin/codecept build -c framework-tests diff --git a/composer.json b/composer.json index 747a5941..f8023eb7 100644 --- a/composer.json +++ b/composer.json @@ -1,13 +1,17 @@ { "name": "codeception/module-symfony", "description": "Codeception module for Symfony framework", - "license": "MIT", "type": "library", + "license": "MIT", "keywords": [ "codeception", "functional testing", "symfony" ], + "homepage": "https://codeception.com/", + "support": { + "docs": "https://codeception.com/docs/modules/Symfony" + }, "authors": [ { "name": "Michael Bodnarchuk" @@ -17,17 +21,16 @@ "homepage": "https://medium.com/@ganieves" } ], - "homepage": "https://codeception.com/", "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", - "codeception/codeception": "^5.0.8", - "codeception/lib-innerbrowser": "^3.1.1 | ^4.0" + "codeception/codeception": "^5.3", + "codeception/lib-innerbrowser": "^3.1 | ^4.0" }, "require-dev": { "codeception/module-asserts": "^3.0", "codeception/module-doctrine": "^3.1", - "doctrine/orm": "^2.10", + "doctrine/orm": "^2.20", "symfony/browser-kit": "^5.4 | ^6.4 | ^7.2", "symfony/cache": "^5.4 | ^6.4 | ^7.2", "symfony/config": "^5.4 | ^6.4 | ^7.2", @@ -62,12 +65,12 @@ "codeception/module-asserts": "Include traditional PHPUnit assertions in your tests", "symfony/web-profiler-bundle": "Tool that gives information about the execution of requests" }, - "minimum-stability": "RC", "autoload": { "classmap": ["src/"] }, "config": { "classmap-authoritative": true, "sort-packages": true - } + }, + "minimum-stability": "RC" } diff --git a/src/Codeception/Lib/Connector/Symfony.php b/src/Codeception/Lib/Connector/Symfony.php index 44d7595a..019317af 100644 --- a/src/Codeception/Lib/Connector/Symfony.php +++ b/src/Codeception/Lib/Connector/Symfony.php @@ -49,7 +49,7 @@ protected function doRequest(object $request): Response } /** - * Reboot kernel + * Reboots the kernel. * * Services from the list of persistent services * are updated from service container before kernel shutdown @@ -66,7 +66,8 @@ public function rebootKernel(): void } $this->persistDoctrineConnections(); - $this->kernel->reboot(null); + $this->ensureKernelShutdown(); + $this->kernel->boot(); $this->container = $this->getContainer(); foreach ($this->persistentServices as $serviceName => $service) { @@ -82,6 +83,12 @@ public function rebootKernel(): void } } + protected function ensureKernelShutdown(): void + { + $this->kernel->boot(); + $this->kernel->shutdown(); + } + private function getContainer(): ?ContainerInterface { /** @var ContainerInterface $container */ diff --git a/src/Codeception/Module/Symfony.php b/src/Codeception/Module/Symfony.php index 0e41e787..3ac2bc79 100644 --- a/src/Codeception/Module/Symfony.php +++ b/src/Codeception/Module/Symfony.php @@ -321,7 +321,7 @@ protected function getKernelClass(): string throw new ModuleRequireException( self::class, "Kernel class was not found.\n" - . 'Specify directory where file with Kernel class for your application is located with `app_path` parameter.' + . 'Specify directory where file with Kernel class for your application is located with `kernel_class` parameter.' ); } diff --git a/src/Codeception/Module/Symfony/BrowserAssertionsTrait.php b/src/Codeception/Module/Symfony/BrowserAssertionsTrait.php index 488e1976..760f6cc7 100644 --- a/src/Codeception/Module/Symfony/BrowserAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/BrowserAssertionsTrait.php @@ -25,6 +25,11 @@ trait BrowserAssertionsTrait { /** * Asserts that the given cookie in the test client is set to the expected value. + * + * ```php + * assertBrowserCookieValueSame('cookie_name', 'expected_value'); + * ``` */ public function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', ?string $domain = null, string $message = ''): void { @@ -35,6 +40,11 @@ public function assertBrowserCookieValueSame(string $name, string $expectedValue /** * Asserts that the test client has the specified cookie set. * This indicates that the cookie was set by any response during the test. + * + * ``` + * assertBrowserHasCookie('cookie_name'); + * ``` */ public function assertBrowserHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void { @@ -44,6 +54,11 @@ public function assertBrowserHasCookie(string $name, string $path = '/', ?string /** * Asserts that the test client does not have the specified cookie set. * This indicates that the cookie was not set by any response during the test. + * + * ```php + * assertBrowserNotHasCookie('cookie_name'); + * ``` */ public function assertBrowserNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void { @@ -52,6 +67,11 @@ public function assertBrowserNotHasCookie(string $name, string $path = '/', ?str /** * Asserts that the specified request attribute matches the expected value. + * + * ```php + * assertRequestAttributeValueSame('attribute_name', 'expected_value'); + * ``` */ public function assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = ''): void { @@ -60,6 +80,11 @@ public function assertRequestAttributeValueSame(string $name, string $expectedVa /** * Asserts that the specified response cookie is present and matches the expected value. + * + * ```php + * assertResponseCookieValueSame('cookie_name', 'expected_value'); + * ``` */ public function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', ?string $domain = null, string $message = ''): void { @@ -69,6 +94,11 @@ public function assertResponseCookieValueSame(string $name, string $expectedValu /** * Asserts that the response format matches the expected format. This checks the format returned by the `Response::getFormat()` method. + * + * ```php + * assertResponseFormatSame('json'); + * ``` */ public function assertResponseFormatSame(?string $expectedFormat, string $message = ''): void { @@ -77,6 +107,11 @@ public function assertResponseFormatSame(?string $expectedFormat, string $messag /** * Asserts that the specified cookie is present in the response. Optionally, it can check for a specific cookie path or domain. + * + * ```php + * assertResponseHasCookie('cookie_name'); + * ``` */ public function assertResponseHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void { @@ -86,6 +121,11 @@ public function assertResponseHasCookie(string $name, string $path = '/', ?strin /** * Asserts that the specified header is available in the response. * For example, use `assertResponseHasHeader('content-type');`. + * + * ```php + * assertResponseHasHeader('content-type'); + * ``` */ public function assertResponseHasHeader(string $headerName, string $message = ''): void { @@ -95,6 +135,11 @@ public function assertResponseHasHeader(string $headerName, string $message = '' /** * Asserts that the specified header does not contain the expected value in the response. * For example, use `assertResponseHeaderNotSame('content-type', 'application/octet-stream');`. + * + * ```php + * assertResponseHeaderNotSame('content-type', 'application/json'); + * ``` */ public function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = ''): void { @@ -104,6 +149,11 @@ public function assertResponseHeaderNotSame(string $headerName, string $expected /** * Asserts that the specified header contains the expected value in the response. * For example, use `assertResponseHeaderSame('content-type', 'application/octet-stream');`. + * + * ```php + * assertResponseHeaderSame('content-type', 'application/json'); + * ``` */ public function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = ''): void { @@ -112,6 +162,11 @@ public function assertResponseHeaderSame(string $headerName, string $expectedVal /** * Asserts that the response was successful (HTTP status code is in the 2xx range). + * + * ```php + * assertResponseIsSuccessful(); + * ``` */ public function assertResponseIsSuccessful(string $message = '', bool $verbose = true): void { @@ -120,6 +175,11 @@ public function assertResponseIsSuccessful(string $message = '', bool $verbose = /** * Asserts that the response is unprocessable (HTTP status code is 422). + * + * ```php + * assertResponseIsUnprocessable(); + * ``` */ public function assertResponseIsUnprocessable(string $message = '', bool $verbose = true): void { @@ -128,6 +188,11 @@ public function assertResponseIsUnprocessable(string $message = '', bool $verbos /** * Asserts that the specified cookie is not present in the response. Optionally, it can check for a specific cookie path or domain. + * + * ```php + * assertResponseNotHasCookie('cookie_name'); + * ``` */ public function assertResponseNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void { @@ -136,7 +201,11 @@ public function assertResponseNotHasCookie(string $name, string $path = '/', ?st /** * Asserts that the specified header is not available in the response. - * For example, use `assertResponseNotHasHeader('content-type');`. + * + * ```php + * assertResponseNotHasHeader('content-type'); + * ``` */ public function assertResponseNotHasHeader(string $headerName, string $message = ''): void { @@ -146,6 +215,12 @@ public function assertResponseNotHasHeader(string $headerName, string $message = /** * Asserts that the response is a redirect. Optionally, you can check the target location and status code. * The expected location can be either an absolute or a relative path. + * + * ```php + * assertResponseRedirects('/login', 302); + * ``` */ public function assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', bool $verbose = true): void { @@ -165,6 +240,11 @@ public function assertResponseRedirects(?string $expectedLocation = null, ?int $ /** * Asserts that the response status code matches the expected code. + * + * ```php + * assertResponseStatusCodeSame(200); + * ``` */ public function assertResponseStatusCodeSame(int $expectedCode, string $message = '', bool $verbose = true): void { @@ -173,13 +253,21 @@ public function assertResponseStatusCodeSame(int $expectedCode, string $message /** * Asserts the request matches the given route and optionally route parameters. + * + * ```php + * assertRouteSame('profile', ['id' => 123]); + * ``` + * + * @param array $parameters */ - public function assertRouteSame(string $expectedRoute, array $parameters = [], string $message = ''): void { + public function assertRouteSame(string $expectedRoute, array $parameters = [], string $message = ''): void + { $request = $this->getClient()->getRequest(); $this->assertThat($request, new RequestAttributeValueSame('_route', $expectedRoute)); foreach ($parameters as $key => $value) { - $this->assertThat($request, new RequestAttributeValueSame($key, $value), $message); + $this->assertThat($request, new RequestAttributeValueSame($key, (string)$value), $message); } } @@ -264,7 +352,7 @@ public function seePageRedirectsTo(string $page, string $redirectsTo): void * ]); * ``` * - * @param string $name The `name` attribute of the `
`. You cannot use an array as a selector here. + * @param string $name The `name` attribute of the ``. You cannot use an array as a selector here. * @param array $fields The form fields to submit. */ public function submitSymfonyForm(string $name, array $fields): void diff --git a/src/Codeception/Module/Symfony/ConsoleAssertionsTrait.php b/src/Codeception/Module/Symfony/ConsoleAssertionsTrait.php index 18e20173..763f977e 100644 --- a/src/Codeception/Module/Symfony/ConsoleAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/ConsoleAssertionsTrait.php @@ -9,6 +9,9 @@ use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\HttpKernel\KernelInterface; +use function in_array; +use function sprintf; + trait ConsoleAssertionsTrait { /** @@ -20,40 +23,42 @@ trait ConsoleAssertionsTrait * $result = $I->runSymfonyConsoleCommand('hello:world', ['arg' => 'argValue', 'opt1' => 'optValue'], ['input']); * ``` * - * @param string $command The console command to execute - * @param array $parameters Parameters (arguments and options) to pass to the command - * @param array $consoleInputs Console inputs (e.g. used for interactive questions) - * @param int $expectedExitCode The expected exit code of the command - * @return string Returns the console output of the command + * @param string $command The console command to execute. + * @param array $parameters Arguments and options passed to the command + * @param list $consoleInputs Inputs for interactive questions. + * @param int $expectedExitCode Expected exit code. + * @return string Console output (stdout). */ - public function runSymfonyConsoleCommand(string $command, array $parameters = [], array $consoleInputs = [], int $expectedExitCode = 0): string - { - $kernel = $this->grabKernelService(); - $application = new Application($kernel); - $consoleCommand = $application->find($command); - $commandTester = new CommandTester($consoleCommand); + public function runSymfonyConsoleCommand( + string $command, + array $parameters = [], + array $consoleInputs = [], + int $expectedExitCode = 0 + ): string { + $kernel = $this->grabKernelService(); + $application = new Application($kernel); + $consoleCommand = $application->find($command); + $commandTester = new CommandTester($consoleCommand); $commandTester->setInputs($consoleInputs); - $input = ['command' => $command] + $parameters; - $options = $this->configureOptions($parameters); - + $input = ['command' => $command] + $parameters; + $options = $this->configureOptions($parameters); $exitCode = $commandTester->execute($input, $options); - $output = $commandTester->getDisplay(); + $output = $commandTester->getDisplay(); $this->assertSame( $expectedExitCode, $exitCode, - sprintf( - 'Command did not exit with code %d but with %d: %s', - $expectedExitCode, - $exitCode, - $output - ) + sprintf('Command exited with %d instead of expected %d. Output: %s', $exitCode, $expectedExitCode, $output) ); return $output; } + /** + * @param array $parameters + * @return array Options array supported by CommandTester. + */ private function configureOptions(array $parameters): array { $options = []; @@ -69,27 +74,24 @@ private function configureOptions(array $parameters): array } if (in_array('--quiet', $parameters, true) || in_array('-q', $parameters, true)) { - $options['verbosity'] = OutputInterface::VERBOSITY_QUIET; + $options['verbosity'] = OutputInterface::VERBOSITY_QUIET; $options['interactive'] = false; } - if ( - in_array('-vvv', $parameters, true) || - in_array('--verbose=3', $parameters, true) || - (isset($parameters["--verbose"]) && $parameters["--verbose"] === 3) + if (in_array('-vvv', $parameters, true) + || in_array('--verbose=3', $parameters, true) + || (isset($parameters['--verbose']) && $parameters['--verbose'] === 3) ) { $options['verbosity'] = OutputInterface::VERBOSITY_DEBUG; - } elseif ( - in_array('-vv', $parameters, true) || - in_array('--verbose=2', $parameters, true) || - (isset($parameters["--verbose"]) && $parameters["--verbose"] === 2) + } elseif (in_array('-vv', $parameters, true) + || in_array('--verbose=2', $parameters, true) + || (isset($parameters['--verbose']) && $parameters['--verbose'] === 2) ) { $options['verbosity'] = OutputInterface::VERBOSITY_VERY_VERBOSE; - } elseif ( - in_array('-v', $parameters, true) || - in_array('--verbose=1', $parameters, true) || - in_array('--verbose', $parameters, true) || - (isset($parameters["--verbose"]) && $parameters["--verbose"] === 1) + } elseif (in_array('-v', $parameters, true) + || in_array('--verbose=1', $parameters, true) + || in_array('--verbose', $parameters, true) + || (isset($parameters['--verbose']) && $parameters['--verbose'] === 1) ) { $options['verbosity'] = OutputInterface::VERBOSITY_VERBOSE; } @@ -101,4 +103,4 @@ protected function grabKernelService(): KernelInterface { return $this->grabService('kernel'); } -} \ No newline at end of file +} diff --git a/src/Codeception/Module/Symfony/DomCrawlerAssertionsTrait.php b/src/Codeception/Module/Symfony/DomCrawlerAssertionsTrait.php index 643e3fd1..f25f9bf3 100644 --- a/src/Codeception/Module/Symfony/DomCrawlerAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/DomCrawlerAssertionsTrait.php @@ -15,6 +15,11 @@ trait DomCrawlerAssertionsTrait { /** * Asserts that the checkbox with the given name is checked. + * + * ```php + * assertCheckboxChecked('agree_terms'); + * ``` */ public function assertCheckboxChecked(string $fieldName, string $message = ''): void { @@ -23,27 +28,46 @@ public function assertCheckboxChecked(string $fieldName, string $message = ''): /** * Asserts that the checkbox with the given name is not checked. + * + * ```php + * assertCheckboxNotChecked('subscribe'); + * ``` */ public function assertCheckboxNotChecked(string $fieldName, string $message = ''): void { - $this->assertThatCrawler(new LogicalNot( - new CrawlerSelectorExists("input[name=\"$fieldName\"]:checked") - ), $message); + $this->assertThatCrawler( + new LogicalNot( + new CrawlerSelectorExists("input[name=\"$fieldName\"]:checked") + ), $message + ); } /** * Asserts that the value of the form input with the given name does not equal the expected value. + * + * ```php + * assertInputValueNotSame('username', 'admin'); + * ``` */ public function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = ''): void { $this->assertThatCrawler(new CrawlerSelectorExists("input[name=\"$fieldName\"]"), $message); - $this->assertThatCrawler(new LogicalNot( - new CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue) - ), $message); + $this->assertThatCrawler( + new LogicalNot( + new CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue) + ), $message + ); } /** * Asserts that the value of the form input with the given name equals the expected value. + * + * ```php + * assertInputValueSame('username', 'johndoe'); + * ``` */ public function assertInputValueSame(string $fieldName, string $expectedValue, string $message = ''): void { @@ -56,6 +80,11 @@ public function assertInputValueSame(string $fieldName, string $expectedValue, s /** * Asserts that the `` element contains the given title. + * + * ```php + * <?php + * $I->assertPageTitleContains('Welcome'); + * ``` */ public function assertPageTitleContains(string $expectedTitle, string $message = ''): void { @@ -64,6 +93,11 @@ public function assertPageTitleContains(string $expectedTitle, string $message = /** * Asserts that the `<title>` element equals the given title. + * + * ```php + * <?php + * $I->assertPageTitleSame('Home Page'); + * ``` */ public function assertPageTitleSame(string $expectedTitle, string $message = ''): void { @@ -72,6 +106,11 @@ public function assertPageTitleSame(string $expectedTitle, string $message = '') /** * Asserts that the given selector matches at least one element in the response. + * + * ```php + * <?php + * $I->assertSelectorExists('.main-content'); + * ``` */ public function assertSelectorExists(string $selector, string $message = ''): void { @@ -80,6 +119,11 @@ public function assertSelectorExists(string $selector, string $message = ''): vo /** * Asserts that the given selector does not match at least one element in the response. + * + * ```php + * <?php + * $I->assertSelectorNotExists('.error'); + * ``` */ public function assertSelectorNotExists(string $selector, string $message = ''): void { @@ -88,6 +132,11 @@ public function assertSelectorNotExists(string $selector, string $message = ''): /** * Asserts that the first element matching the given selector contains the expected text. + * + * ```php + * <?php + * $I->assertSelectorTextContains('h1', 'Dashboard'); + * ``` */ public function assertSelectorTextContains(string $selector, string $text, string $message = ''): void { @@ -97,6 +146,11 @@ public function assertSelectorTextContains(string $selector, string $text, strin /** * Asserts that the first element matching the given selector does not contain the expected text. + * + * ```php + * <?php + * $I->assertSelectorTextNotContains('p', 'error'); + * ``` */ public function assertSelectorTextNotContains(string $selector, string $text, string $message = ''): void { @@ -106,6 +160,11 @@ public function assertSelectorTextNotContains(string $selector, string $text, st /** * Asserts that the text of the first element matching the given selector equals the expected text. + * + * ```php + * <?php + * $I->assertSelectorTextSame('h1', 'Dashboard'); + * ``` */ public function assertSelectorTextSame(string $selector, string $text, string $message = ''): void { diff --git a/src/Codeception/Module/Symfony/FormAssertionsTrait.php b/src/Codeception/Module/Symfony/FormAssertionsTrait.php index cdebbb64..f77403bd 100644 --- a/src/Codeception/Module/Symfony/FormAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/FormAssertionsTrait.php @@ -14,6 +14,11 @@ trait FormAssertionsTrait { /** * Asserts that value of the field of the first form matching the given selector does equal the expected value. + * + * ```php + * <?php + * $I->assertFormValue('#loginForm', 'username', 'john_doe'); + * ``` */ public function assertFormValue(string $formSelector, string $fieldName, string $value, string $message = ''): void { @@ -25,7 +30,12 @@ public function assertFormValue(string $formSelector, string $fieldName, string } /** - * Asserts that value of the field of the first form matching the given selector does equal the expected value. + * Asserts that the field of the first form matching the given selector does not have a value. + * + * ```php + * <?php + * $I->assertNoFormValue('#registrationForm', 'middle_name'); + * ``` */ public function assertNoFormValue(string $formSelector, string $fieldName, string $message = ''): void { @@ -128,7 +138,6 @@ public function seeFormErrorMessage(string $field, ?string $message = null): voi * If you want to specify the error messages, you can do so * by sending an associative array instead, with the key being * the name of the field and the error message the value. - * * This method will validate that the expected error message * is contained in the actual error message, that is, * you can specify either the entire error message or just a part of it: @@ -136,7 +145,7 @@ public function seeFormErrorMessage(string $field, ?string $message = null): voi * ```php * <?php * $I->seeFormErrorMessages([ - * 'address' => 'The address is too long' + * 'address' => 'The address is too long', * 'telephone' => 'too short', // the full error message is 'The telephone is too short' * ]); * ``` @@ -191,4 +200,4 @@ protected function grabFormCollector(string $function): FormDataCollector { return $this->grabCollector('form', $function); } -} \ No newline at end of file +} diff --git a/src/Codeception/Module/Symfony/HttpClientAssertionsTrait.php b/src/Codeception/Module/Symfony/HttpClientAssertionsTrait.php index 9ac3a6e4..f6f322eb 100644 --- a/src/Codeception/Module/Symfony/HttpClientAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/HttpClientAssertionsTrait.php @@ -12,7 +12,18 @@ 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.) + * 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.) + * + * ```php + * <?php + * $I->assertHttpClientRequest( + * 'https://example.com/api', + * 'POST', + * '{"data": "value"}', + * ['Authorization' => 'Bearer token'] + * ); + * ``` */ public function assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array|null $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client'): void { @@ -77,6 +88,11 @@ public function assertHttpClientRequest(string $expectedUrl, string $expectedMet /** * 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. + * + * ```php + * <?php + * $I->assertHttpClientRequestCount(3); + * ``` */ public function assertHttpClientRequestCount(int $count, string $httpClientId = 'http_client'): void { @@ -88,6 +104,11 @@ public function assertHttpClientRequestCount(int $count, string $httpClientId = /** * 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. + * + * ```php + * <?php + * $I->assertNotHttpClientRequest('https://example.com/unexpected', 'GET'); + * ``` */ public function assertNotHttpClientRequest(string $unexpectedUrl, string $expectedMethod = 'GET', string $httpClientId = 'http_client'): void { diff --git a/src/Codeception/Module/Symfony/MailerAssertionsTrait.php b/src/Codeception/Module/Symfony/MailerAssertionsTrait.php index df2fd0f1..5a31e6d8 100644 --- a/src/Codeception/Module/Symfony/MailerAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/MailerAssertionsTrait.php @@ -15,6 +15,11 @@ trait MailerAssertionsTrait { /** * Asserts that the expected number of emails was sent. + * + * ```php + * <?php + * $I->assertEmailCount(2, 'smtp'); + * ``` */ public function assertEmailCount(int $count, ?string $transport = null, string $message = ''): void { @@ -24,6 +29,12 @@ public function assertEmailCount(int $count, ?string $transport = null, string $ /** * Asserts that the given mailer event is not queued. * Use `getMailerEvent(int $index = 0, ?string $transport = null)` to retrieve a mailer event by index. + * + * ```php + * <?php + * $event = $I->getMailerEvent(); + * $I->assertEmailIsNotQueued($event); + * ``` */ public function assertEmailIsNotQueued(MessageEvent $event, string $message = ''): void { @@ -33,6 +44,12 @@ public function assertEmailIsNotQueued(MessageEvent $event, string $message = '' /** * Asserts that the given mailer event is queued. * Use `getMailerEvent(int $index = 0, ?string $transport = null)` to retrieve a mailer event by index. + * + * ```php + * <?php + * $event = $I->getMailerEvent(); + * $I->assertEmailIsQueued($event); + * ``` */ public function assertEmailIsQueued(MessageEvent $event, string $message = ''): void { @@ -41,6 +58,11 @@ public function assertEmailIsQueued(MessageEvent $event, string $message = ''): /** * Asserts that the expected number of emails was queued (e.g. using the Messenger component). + * + * ```php + * <?php + * $I->assertQueuedEmailCount(1, 'smtp'); + * ``` */ public function assertQueuedEmailCount(int $count, ?string $transport = null, string $message = ''): void { @@ -50,7 +72,12 @@ public function assertQueuedEmailCount(int $count, ?string $transport = null, st /** * 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. + * If your app performs an HTTP redirect, you need to suppress it using [stopFollowingRedirects()](#stopFollowingRedirects) first; otherwise this check will *always* pass. + * + * ```php + * <?php + * $I->dontSeeEmailIsSent(); + * ``` */ public function dontSeeEmailIsSent(): void { @@ -60,7 +87,7 @@ public function dontSeeEmailIsSent(): void /** * Returns the last sent email. * The function 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. + * If your app performs an HTTP redirect after sending the email, you need to suppress it using [stopFollowingRedirects()](#stopFollowingRedirects) first. * See also: [grabSentEmails()](https://codeception.com/docs/modules/Symfony#grabSentEmails) * * ```php @@ -82,7 +109,7 @@ public function grabLastSentEmail(): ?Email /** * Returns an array of all sent emails. * The function 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. + * If your app performs an HTTP redirect after sending the email, you need to suppress it using [stopFollowingRedirects()](#stopFollowingRedirects) first. * See also: [grabLastSentEmail()](https://codeception.com/docs/modules/Symfony#grabLastSentEmail) * * ```php @@ -100,7 +127,12 @@ public function grabSentEmails(): array /** * 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. + * If your app performs an HTTP redirect after sending the email, you need to suppress it using [stopFollowingRedirects()](#stopFollowingRedirects) first. + * + * Limitation: + * If your mail is sent in a Symfony console command and you start that command in your test with [$I->runShellCommand()](https://codeception.com/docs/modules/Cli#runShellCommand), + * Codeception will not notice it. + * As a more professional alternative, we recommend Mailpit (see [Addons](https://codeception.com/addons)), which also lets you test the content of the mail. * * ```php * <?php @@ -114,18 +146,31 @@ public function seeEmailIsSent(int $expectedCount = 1): void $this->assertThat($this->getMessageMailerEvents(), new MailerConstraint\EmailCount($expectedCount)); } + /** + * Returns the mailer event at the specified index. + * + * ```php + * <?php + * $event = $I->getMailerEvent(); + * ``` + */ + public function getMailerEvent(int $index = 0, ?string $transport = null): ?MessageEvent + { + $mailerEvents = $this->getMessageMailerEvents(); + $events = $mailerEvents->getEvents($transport); + return $events[$index] ?? null; + } + protected function getMessageMailerEvents(): MessageEvents { - if ($messageLogger = $this->getService('mailer.message_logger_listener')) { - /** @var MessageLoggerListener $messageLogger */ - return $messageLogger->getEvents(); + if ($mailer = $this->getService('mailer.message_logger_listener')) { + /** @var MessageLoggerListener $mailer */ + return $mailer->getEvents(); } - - if ($messageLogger = $this->getService('mailer.logger_message_listener')) { - /** @var MessageLoggerListener $messageLogger */ - return $messageLogger->getEvents(); + if ($mailer = $this->getService('mailer.logger_message_listener')) { + /** @var MessageLoggerListener $mailer */ + return $mailer->getEvents(); } - $this->fail("Emails can't be tested without Symfony Mailer service."); } } diff --git a/src/Codeception/Module/Symfony/MimeAssertionsTrait.php b/src/Codeception/Module/Symfony/MimeAssertionsTrait.php index ba2ee9ac..d48df3d4 100644 --- a/src/Codeception/Module/Symfony/MimeAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/MimeAssertionsTrait.php @@ -4,9 +4,9 @@ namespace Codeception\Module\Symfony; +use PHPUnit\Framework\Assert; 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 @@ -171,8 +171,8 @@ private function verifyEmailObject(?Email $email, string $function): Email { $email = $email ?: $this->grabLastSentEmail(); $errorMsgTemplate = "There is no email to verify. An Email object was not specified when invoking '%s' and the application has not sent one."; - return $email ?: $this->fail( + return $email ?? Assert::fail( sprintf($errorMsgTemplate, $function) ); } -} \ No newline at end of file +} diff --git a/src/Codeception/Module/Symfony/ServicesAssertionsTrait.php b/src/Codeception/Module/Symfony/ServicesAssertionsTrait.php index bd9140c0..1286e252 100644 --- a/src/Codeception/Module/Symfony/ServicesAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/ServicesAssertionsTrait.php @@ -5,14 +5,15 @@ namespace Codeception\Module\Symfony; use Codeception\Lib\Connector\Symfony as SymfonyConnector; +use PHPUnit\Framework\Assert; trait ServicesAssertionsTrait { /** * Grabs a service from the Symfony dependency injection container (DIC). - * In "test" environment, Symfony uses a special `test.service_container`. + * In the "test" environment, Symfony uses a special `test.service_container`. * See the "[Public Versus Private Services](https://symfony.com/doc/current/service_container/alias_private.html#marking-services-as-public-private)" documentation. - * Services that aren't injected somewhere into your app, need to be defined as `public` to be accessible by Codeception. + * Services that aren't injected anywhere in your app, need to be defined as `public` to be accessible by Codeception. * * ```php * <?php @@ -24,8 +25,10 @@ trait ServicesAssertionsTrait public function grabService(string $serviceId): object { if (!$service = $this->getService($serviceId)) { - $this->fail("Service `{$serviceId}` is required by Codeception, but not loaded by Symfony since you're not using it anywhere in your app.\n - Recommended solution: Set it to `public` in your `config/services_test.php`/`.yaml`, see https://symfony.com/doc/current/service_container/alias_private.html#marking-services-as-public-private"); + Assert::fail("Service `{$serviceId}` is required by Codeception, but not loaded by Symfony. Possible solutions:\n + In your `config/packages/framework.php`/`.yaml`, set `test` to `true` (when in test environment), see https://symfony.com/doc/current/reference/configuration/framework.html#test\n + If you're still getting this message, you're not using that service in your app, so Symfony isn't loading it at all.\n + Solution: Set it to `public` in your `config/services.php`/`.yaml`, see https://symfony.com/doc/current/service_container/alias_private.html#marking-services-as-public-private\n"); } return $service; diff --git a/src/Codeception/Module/Symfony/TranslationAssertionsTrait.php b/src/Codeception/Module/Symfony/TranslationAssertionsTrait.php index 7c4c385a..5fa91725 100644 --- a/src/Codeception/Module/Symfony/TranslationAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/TranslationAssertionsTrait.php @@ -109,7 +109,7 @@ public function seeDefaultLocaleIs(string $expectedLocale): void * $I->seeFallbackLocalesAre(['es', 'fr']); * ``` * - * @param array $expectedLocales The expected fallback locales + * @param string[] $expectedLocales The expected fallback locales */ public function seeFallbackLocalesAre(array $expectedLocales): void { diff --git a/src/Codeception/Module/Symfony/TwigAssertionsTrait.php b/src/Codeception/Module/Symfony/TwigAssertionsTrait.php index 1bfba3ec..e664932c 100644 --- a/src/Codeception/Module/Symfony/TwigAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/TwigAssertionsTrait.php @@ -79,4 +79,4 @@ protected function grabTwigCollector(string $function): TwigDataCollector { return $this->grabCollector('twig', $function); } -} \ No newline at end of file +} diff --git a/src/Codeception/Module/Symfony/ValidatorAssertionsTrait.php b/src/Codeception/Module/Symfony/ValidatorAssertionsTrait.php index ca82e196..508cfa5e 100644 --- a/src/Codeception/Module/Symfony/ValidatorAssertionsTrait.php +++ b/src/Codeception/Module/Symfony/ValidatorAssertionsTrait.php @@ -20,7 +20,7 @@ trait ValidatorAssertionsTrait * $I->dontSeeViolatedConstraint($subject, 'propertyName', 'Symfony\Validator\ConstraintClass'); * ``` */ - public function dontSeeViolatedConstraint(mixed $subject, ?string $propertyPath = null, ?string $constraint = null): void + public function dontSeeViolatedConstraint(object $subject, ?string $propertyPath = null, ?string $constraint = null): void { $violations = $this->getViolationsForSubject($subject, $propertyPath, $constraint); $this->assertCount(0, $violations, 'Constraint violations found.'); @@ -37,7 +37,7 @@ public function dontSeeViolatedConstraint(mixed $subject, ?string $propertyPath * $I->seeViolatedConstraint($subject, 'propertyName', 'Symfony\Validator\ConstraintClass'); * ``` */ - public function seeViolatedConstraint(mixed $subject, ?string $propertyPath = null, ?string $constraint = null): void + public function seeViolatedConstraint(object $subject, ?string $propertyPath = null, ?string $constraint = null): void { $violations = $this->getViolationsForSubject($subject, $propertyPath, $constraint); $this->assertNotCount(0, $violations, 'No constraint violations found.'); @@ -52,7 +52,7 @@ public function seeViolatedConstraint(mixed $subject, ?string $propertyPath = nu * $I->seeViolatedConstraintsCount(2, $subject, 'propertyName'); * ``` */ - public function seeViolatedConstraintsCount(int $expected, mixed $subject, ?string $propertyPath = null, ?string $constraint = null): void + public function seeViolatedConstraintsCount(int $expected, object $subject, ?string $propertyPath = null, ?string $constraint = null): void { $violations = $this->getViolationsForSubject($subject, $propertyPath, $constraint); $this->assertCount($expected, $violations); @@ -66,12 +66,12 @@ public function seeViolatedConstraintsCount(int $expected, mixed $subject, ?stri * $I->seeViolatedConstraintMessage('too short', $user, 'address'); * ``` */ - public function seeViolatedConstraintMessage(string $expected, mixed $subject, string $propertyPath): void + public function seeViolatedConstraintMessage(string $expected, object $subject, string $propertyPath): void { $violations = $this->getViolationsForSubject($subject, $propertyPath); $containsExpected = false; foreach ($violations as $violation) { - if ($violation->getPropertyPath() === $propertyPath && str_contains($violation->getMessage(), $expected)) { + if ($violation->getPropertyPath() === $propertyPath && str_contains((string)$violation->getMessage(), $expected)) { $containsExpected = true; break; } @@ -81,7 +81,7 @@ public function seeViolatedConstraintMessage(string $expected, mixed $subject, s } /** @return ConstraintViolationInterface[] */ - protected function getViolationsForSubject(mixed $subject, ?string $propertyPath = null, ?string $constraint = null): array + protected function getViolationsForSubject(object $subject, ?string $propertyPath = null, ?string $constraint = null): array { $validator = $this->getValidatorService(); $violations = $propertyPath ? $validator->validateProperty($subject, $propertyPath) : $validator->validate($subject); @@ -89,9 +89,9 @@ protected function getViolationsForSubject(mixed $subject, ?string $propertyPath $violations = iterator_to_array($violations); if ($constraint !== null) { - return array_filter( + return (array)array_filter( $violations, - static fn($violation): bool => $violation->getConstraint()::class === $constraint && + static fn(ConstraintViolationInterface $violation): bool => get_class((object)$violation->getConstraint()) === $constraint && ($propertyPath === null || $violation->getPropertyPath() === $propertyPath) ); } <!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