From 7a4d29d561c76d9f292a6b6441acdc16464f72f6 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 2 Oct 2024 17:01:06 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B07870=20:white=5Fcheck=5Fmark:=20Prerequi?= =?UTF-8?q?sites=20to=20test=20portal=20services=20(instantiate=20a=20symf?= =?UTF-8?q?ony=20service)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../portal/config/packages/web_profiler.yaml | 5 - lib/composer/autoload_classmap.php | 10 + lib/composer/autoload_static.php | 10 + lib/composer/installed.php | 4 +- .../Test/BrowserKitAssertionsTrait.php | 197 ++++++++++++++++++ .../Test/DomCrawlerAssertionsTrait.php | 151 ++++++++++++++ .../Test/HttpClientAssertionsTrait.php | 134 ++++++++++++ .../framework-bundle/Test/KernelTestCase.php | 137 ++++++++++++ .../Test/MailerAssertionsTrait.php | 138 ++++++++++++ .../Test/NotificationAssertionsTrait.php | 100 +++++++++ .../Test/TestBrowserToken.php | 58 ++++++ .../framework-bundle/Test/TestContainer.php | 126 +++++++++++ .../Test/WebTestAssertionsTrait.php | 19 ++ .../framework-bundle/Test/WebTestCase.php | 59 ++++++ .../Dependencies/Composer/iTopComposer.php | 2 +- .../module_integration.xml.dist | 2 +- tests/php-unit-tests/phpunit.xml.dist | 2 +- .../postbuild_integration.xml.dist | 2 +- .../src/BaseTestCase/ItopTestCase.php | 39 +++- .../php-unit-tests/BootSymfonyKernelTest.php | 49 +++++ tests/php-unit-tests/unittestautoload.php | 6 +- 21 files changed, 1233 insertions(+), 17 deletions(-) create mode 100644 lib/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php create mode 100644 lib/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php create mode 100644 lib/symfony/framework-bundle/Test/HttpClientAssertionsTrait.php create mode 100644 lib/symfony/framework-bundle/Test/KernelTestCase.php create mode 100644 lib/symfony/framework-bundle/Test/MailerAssertionsTrait.php create mode 100644 lib/symfony/framework-bundle/Test/NotificationAssertionsTrait.php create mode 100644 lib/symfony/framework-bundle/Test/TestBrowserToken.php create mode 100644 lib/symfony/framework-bundle/Test/TestContainer.php create mode 100644 lib/symfony/framework-bundle/Test/WebTestAssertionsTrait.php create mode 100644 lib/symfony/framework-bundle/Test/WebTestCase.php create mode 100644 tests/php-unit-tests/unitary-tests/tests/php-unit-tests/BootSymfonyKernelTest.php diff --git a/datamodels/2.x/itop-portal-base/portal/config/packages/web_profiler.yaml b/datamodels/2.x/itop-portal-base/portal/config/packages/web_profiler.yaml index 7676f2292..4c93561e8 100644 --- a/datamodels/2.x/itop-portal-base/portal/config/packages/web_profiler.yaml +++ b/datamodels/2.x/itop-portal-base/portal/config/packages/web_profiler.yaml @@ -4,9 +4,4 @@ when@dev: toolbar: true intercept_redirects: false -when@test: - web_profiler: - toolbar: false - intercept_redirects: false - diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 6c8ee08cc..e30582e5f 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -1778,6 +1778,16 @@ return array( 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\AbstractVault' => $vendorDir . '/symfony/framework-bundle/Secrets/AbstractVault.php', 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\DotenvVault' => $vendorDir . '/symfony/framework-bundle/Secrets/DotenvVault.php', 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\SodiumVault' => $vendorDir . '/symfony/framework-bundle/Secrets/SodiumVault.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\BrowserKitAssertionsTrait' => $vendorDir . '/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\DomCrawlerAssertionsTrait' => $vendorDir . '/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\HttpClientAssertionsTrait' => $vendorDir . '/symfony/framework-bundle/Test/HttpClientAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase' => $vendorDir . '/symfony/framework-bundle/Test/KernelTestCase.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\MailerAssertionsTrait' => $vendorDir . '/symfony/framework-bundle/Test/MailerAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\NotificationAssertionsTrait' => $vendorDir . '/symfony/framework-bundle/Test/NotificationAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\TestBrowserToken' => $vendorDir . '/symfony/framework-bundle/Test/TestBrowserToken.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\TestContainer' => $vendorDir . '/symfony/framework-bundle/Test/TestContainer.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestAssertionsTrait' => $vendorDir . '/symfony/framework-bundle/Test/WebTestAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase' => $vendorDir . '/symfony/framework-bundle/Test/WebTestCase.php', 'Symfony\\Bundle\\FrameworkBundle\\Translation\\Translator' => $vendorDir . '/symfony/framework-bundle/Translation/Translator.php', 'Symfony\\Bundle\\TwigBundle\\CacheWarmer\\TemplateCacheWarmer' => $vendorDir . '/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php', 'Symfony\\Bundle\\TwigBundle\\Command\\LintCommand' => $vendorDir . '/symfony/twig-bundle/Command/LintCommand.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 3b2a23078..ac3a90c8e 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -2158,6 +2158,16 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\AbstractVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/AbstractVault.php', 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\DotenvVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/DotenvVault.php', 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\SodiumVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/SodiumVault.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\BrowserKitAssertionsTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\DomCrawlerAssertionsTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\HttpClientAssertionsTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/HttpClientAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/KernelTestCase.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\MailerAssertionsTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/MailerAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\NotificationAssertionsTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/NotificationAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\TestBrowserToken' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/TestBrowserToken.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\TestContainer' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/TestContainer.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestAssertionsTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/WebTestAssertionsTrait.php', + 'Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/WebTestCase.php', 'Symfony\\Bundle\\FrameworkBundle\\Translation\\Translator' => __DIR__ . '/..' . '/symfony/framework-bundle/Translation/Translator.php', 'Symfony\\Bundle\\TwigBundle\\CacheWarmer\\TemplateCacheWarmer' => __DIR__ . '/..' . '/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php', 'Symfony\\Bundle\\TwigBundle\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/twig-bundle/Command/LintCommand.php', diff --git a/lib/composer/installed.php b/lib/composer/installed.php index 4ceb53402..b30a7b375 100644 --- a/lib/composer/installed.php +++ b/lib/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'combodo/itop', 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => 'd53d4970f40cbc0a8c5cd6e1d5cc3ca06ac4719e', + 'reference' => '5ae2fdee94b925808451355b98b941351e0b4fcd', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -22,7 +22,7 @@ 'combodo/itop' => array( 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => 'd53d4970f40cbc0a8c5cd6e1d5cc3ca06ac4719e', + 'reference' => '5ae2fdee94b925808451355b98b941351e0b4fcd', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/lib/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php b/lib/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php new file mode 100644 index 000000000..a6d4fed33 --- /dev/null +++ b/lib/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php @@ -0,0 +1,197 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\Constraint\LogicalAnd; +use PHPUnit\Framework\Constraint\LogicalNot; +use PHPUnit\Framework\ExpectationFailedException; +use Symfony\Component\BrowserKit\AbstractBrowser; +use Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Test\Constraint as ResponseConstraint; + +/** + * Ideas borrowed from Laravel Dusk's assertions. + * + * @see https://laravel.com/docs/5.7/dusk#available-assertions + */ +trait BrowserKitAssertionsTrait +{ + public static function assertResponseIsSuccessful(string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseIsSuccessful(), $message); + } + + public static function assertResponseStatusCodeSame(int $expectedCode, string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message); + } + + public static function assertResponseFormatSame(?string $expectedFormat, string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseFormatSame(self::getRequest(), $expectedFormat), $message); + } + + public static function assertResponseRedirects(string $expectedLocation = null, int $expectedCode = null, string $message = ''): void + { + $constraint = new ResponseConstraint\ResponseIsRedirected(); + if ($expectedLocation) { + if (class_exists(ResponseConstraint\ResponseHeaderLocationSame::class)) { + $locationConstraint = new ResponseConstraint\ResponseHeaderLocationSame(self::getRequest(), $expectedLocation); + } else { + $locationConstraint = new ResponseConstraint\ResponseHeaderSame('Location', $expectedLocation); + } + + $constraint = LogicalAnd::fromConstraints($constraint, $locationConstraint); + } + if ($expectedCode) { + $constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseStatusCodeSame($expectedCode)); + } + + self::assertThatForResponse($constraint, $message); + } + + public static function assertResponseHasHeader(string $headerName, string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseHasHeader($headerName), $message); + } + + public static function assertResponseNotHasHeader(string $headerName, string $message = ''): void + { + self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHasHeader($headerName)), $message); + } + + public static function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue), $message); + } + + public static function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message); + } + + public static function assertResponseHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message); + } + + public static function assertResponseNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message); + } + + public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThatForResponse(LogicalAnd::fromConstraints( + new ResponseConstraint\ResponseHasCookie($name, $path, $domain), + new ResponseConstraint\ResponseCookieValueSame($name, $expectedValue, $path, $domain) + ), $message); + } + + public static function assertResponseIsUnprocessable(string $message = ''): void + { + self::assertThatForResponse(new ResponseConstraint\ResponseIsUnprocessable(), $message); + } + + public static function assertBrowserHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThatForClient(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), $message); + } + + public static function assertBrowserNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThatForClient(new LogicalNot(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain)), $message); + } + + public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThatForClient(LogicalAnd::fromConstraints( + new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), + new BrowserKitConstraint\BrowserCookieValueSame($name, $expectedValue, $raw, $path, $domain) + ), $message); + } + + public static function assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getRequest(), new ResponseConstraint\RequestAttributeValueSame($name, $expectedValue), $message); + } + + public static function assertRouteSame(string $expectedRoute, array $parameters = [], string $message = ''): void + { + $constraint = new ResponseConstraint\RequestAttributeValueSame('_route', $expectedRoute); + $constraints = []; + foreach ($parameters as $key => $value) { + $constraints[] = new ResponseConstraint\RequestAttributeValueSame($key, $value); + } + if ($constraints) { + $constraint = LogicalAnd::fromConstraints($constraint, ...$constraints); + } + + self::assertThat(self::getRequest(), $constraint, $message); + } + + public static function assertThatForResponse(Constraint $constraint, string $message = ''): void + { + try { + self::assertThat(self::getResponse(), $constraint, $message); + } catch (ExpectationFailedException $exception) { + if (($serverExceptionMessage = self::getResponse()->headers->get('X-Debug-Exception')) + && ($serverExceptionFile = self::getResponse()->headers->get('X-Debug-Exception-File'))) { + $serverExceptionFile = explode(':', $serverExceptionFile); + $exception->__construct($exception->getMessage(), $exception->getComparisonFailure(), new \ErrorException(rawurldecode($serverExceptionMessage), 0, 1, rawurldecode($serverExceptionFile[0]), $serverExceptionFile[1]), $exception->getPrevious()); + } + + throw $exception; + } + } + + public static function assertThatForClient(Constraint $constraint, string $message = ''): void + { + self::assertThat(self::getClient(), $constraint, $message); + } + + protected static function getClient(AbstractBrowser $newClient = null): ?AbstractBrowser + { + static $client; + + if (0 < \func_num_args()) { + return $client = $newClient; + } + + if (!$client instanceof AbstractBrowser) { + static::fail(sprintf('A client must be set to make assertions on it. Did you forget to call "%s::createClient()"?', __CLASS__)); + } + + return $client; + } + + private static function getResponse(): Response + { + if (!$response = self::getClient()->getResponse()) { + static::fail('A client must have an HTTP Response to make assertions. Did you forget to make an HTTP request?'); + } + + return $response; + } + + private static function getRequest(): Request + { + if (!$request = self::getClient()->getRequest()) { + static::fail('A client must have an HTTP Request to make assertions. Did you forget to make an HTTP request?'); + } + + return $request; + } +} diff --git a/lib/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php b/lib/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php new file mode 100644 index 000000000..a16709461 --- /dev/null +++ b/lib/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\Constraint\LogicalAnd; +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Component\DomCrawler\Test\Constraint as DomCrawlerConstraint; +use Symfony\Component\DomCrawler\Test\Constraint\CrawlerSelectorExists; + +/** + * Ideas borrowed from Laravel Dusk's assertions. + * + * @see https://laravel.com/docs/5.7/dusk#available-assertions + */ +trait DomCrawlerAssertionsTrait +{ + public static function assertSelectorExists(string $selector, string $message = ''): void + { + self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorExists($selector), $message); + } + + public static function assertSelectorNotExists(string $selector, string $message = ''): void + { + self::assertThat(self::getCrawler(), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorExists($selector)), $message); + } + + public static function assertSelectorCount(int $expectedCount, string $selector, string $message = ''): void + { + self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorCount($expectedCount, $selector), $message); + } + + public static function assertSelectorTextContains(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text) + ), $message); + } + + public static function assertAnySelectorTextContains(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new DomCrawlerConstraint\CrawlerAnySelectorTextContains($selector, $text) + ), $message); + } + + public static function assertSelectorTextSame(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new DomCrawlerConstraint\CrawlerSelectorTextSame($selector, $text) + ), $message); + } + + public static function assertAnySelectorTextSame(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new DomCrawlerConstraint\CrawlerAnySelectorTextSame($selector, $text) + ), $message); + } + + public static function assertSelectorTextNotContains(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text)) + ), $message); + } + + public static function assertAnySelectorTextNotContains(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new LogicalNot(new DomCrawlerConstraint\CrawlerAnySelectorTextContains($selector, $text)) + ), $message); + } + + public static function assertPageTitleSame(string $expectedTitle, string $message = ''): void + { + self::assertSelectorTextSame('title', $expectedTitle, $message); + } + + public static function assertPageTitleContains(string $expectedTitle, string $message = ''): void + { + self::assertSelectorTextContains('title', $expectedTitle, $message); + } + + public static function assertInputValueSame(string $fieldName, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"), + new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue) + ), $message); + } + + public static function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"), + new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue)) + ), $message); + } + + public static function assertCheckboxChecked(string $fieldName, string $message = ''): void + { + self::assertThat(self::getCrawler(), new CrawlerSelectorExists("input[name=\"$fieldName\"]:checked"), $message); + } + + public static function assertCheckboxNotChecked(string $fieldName, string $message = ''): void + { + self::assertThat(self::getCrawler(), new LogicalNot(new CrawlerSelectorExists("input[name=\"$fieldName\"]:checked")), $message); + } + + public static function assertFormValue(string $formSelector, string $fieldName, string $value, string $message = ''): void + { + $node = self::getCrawler()->filter($formSelector); + self::assertNotEmpty($node, sprintf('Form "%s" not found.', $formSelector)); + $values = $node->form()->getValues(); + self::assertArrayHasKey($fieldName, $values, $message ?: sprintf('Field "%s" not found in form "%s".', $fieldName, $formSelector)); + self::assertSame($value, $values[$fieldName]); + } + + public static function assertNoFormValue(string $formSelector, string $fieldName, string $message = ''): void + { + $node = self::getCrawler()->filter($formSelector); + self::assertNotEmpty($node, sprintf('Form "%s" not found.', $formSelector)); + $values = $node->form()->getValues(); + self::assertArrayNotHasKey($fieldName, $values, $message ?: sprintf('Field "%s" has a value in form "%s".', $fieldName, $formSelector)); + } + + private static function getCrawler(): Crawler + { + if (!$crawler = self::getClient()->getCrawler()) { + static::fail('A client must have a crawler to make assertions. Did you forget to make an HTTP request?'); + } + + return $crawler; + } +} diff --git a/lib/symfony/framework-bundle/Test/HttpClientAssertionsTrait.php b/lib/symfony/framework-bundle/Test/HttpClientAssertionsTrait.php new file mode 100644 index 000000000..bed835fa1 --- /dev/null +++ b/lib/symfony/framework-bundle/Test/HttpClientAssertionsTrait.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; + +/* + * @author Mathieu Santostefano + */ + +trait HttpClientAssertionsTrait +{ + public static function assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client'): void + { + /** @var KernelBrowser $client */ + $client = static::getClient(); + + if (!($profile = $client->getProfile())) { + static::fail('The Profiler must be enabled for the current request. Please ensure to call "$client->enableProfiler()" before making the request.'); + } + + /** @var HttpClientDataCollector $httpClientDataCollector */ + $httpClientDataCollector = $profile->getCollector('http_client'); + $expectedRequestHasBeenFound = false; + + if (!\array_key_exists($httpClientId, $httpClientDataCollector->getClients())) { + static::fail(sprintf('HttpClient "%s" is not registered.', $httpClientId)); + } + + foreach ($httpClientDataCollector->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; + } + + self::assertTrue($expectedRequestHasBeenFound, 'The expected request has not been called: "'.$expectedMethod.'" - "'.$expectedUrl.'"'); + } + + public function assertNotHttpClientRequest(string $unexpectedUrl, string $expectedMethod = 'GET', string $httpClientId = 'http_client'): void + { + /** @var KernelBrowser $client */ + $client = static::getClient(); + + if (!$profile = $client->getProfile()) { + static::fail('The Profiler must be enabled for the current request. Please ensure to call "$client->enableProfiler()" before making the request.'); + } + + /** @var HttpClientDataCollector $httpClientDataCollector */ + $httpClientDataCollector = $profile->getCollector('http_client'); + $unexpectedUrlHasBeenFound = false; + + if (!\array_key_exists($httpClientId, $httpClientDataCollector->getClients())) { + static::fail(sprintf('HttpClient "%s" is not registered.', $httpClientId)); + } + + foreach ($httpClientDataCollector->getClients()[$httpClientId]['traces'] as $trace) { + if (($unexpectedUrl === $trace['info']['url'] || $unexpectedUrl === $trace['url']) + && $expectedMethod === $trace['method'] + ) { + $unexpectedUrlHasBeenFound = true; + break; + } + } + + self::assertFalse($unexpectedUrlHasBeenFound, sprintf('Unexpected URL called: "%s" - "%s"', $expectedMethod, $unexpectedUrl)); + } + + public static function assertHttpClientRequestCount(int $count, string $httpClientId = 'http_client'): void + { + /** @var KernelBrowser $client */ + $client = static::getClient(); + + if (!($profile = $client->getProfile())) { + static::fail('The Profiler must be enabled for the current request. Please ensure to call "$client->enableProfiler()" before making the request.'); + } + + /** @var HttpClientDataCollector $httpClientDataCollector */ + $httpClientDataCollector = $profile->getCollector('http_client'); + + self::assertCount($count, $httpClientDataCollector->getClients()[$httpClientId]['traces']); + } +} diff --git a/lib/symfony/framework-bundle/Test/KernelTestCase.php b/lib/symfony/framework-bundle/Test/KernelTestCase.php new file mode 100644 index 000000000..8d27b757f --- /dev/null +++ b/lib/symfony/framework-bundle/Test/KernelTestCase.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * KernelTestCase is the base class for tests needing a Kernel. + * + * @author Fabien Potencier + */ +abstract class KernelTestCase extends TestCase +{ + use MailerAssertionsTrait; + use NotificationAssertionsTrait; + + protected static $class; + + /** + * @var KernelInterface + */ + protected static $kernel; + + protected static $booted = false; + + protected function tearDown(): void + { + static::ensureKernelShutdown(); + static::$class = null; + static::$kernel = null; + static::$booted = false; + } + + /** + * @throws \RuntimeException + * @throws \LogicException + */ + protected static function getKernelClass(): string + { + if (!isset($_SERVER['KERNEL_CLASS']) && !isset($_ENV['KERNEL_CLASS'])) { + throw new \LogicException(sprintf('You must set the KERNEL_CLASS environment variable to the fully-qualified class name of your Kernel in phpunit.xml / phpunit.xml.dist or override the "%1$s::createKernel()" or "%1$s::getKernelClass()" method.', static::class)); + } + + if (!class_exists($class = $_ENV['KERNEL_CLASS'] ?? $_SERVER['KERNEL_CLASS'])) { + throw new \RuntimeException(sprintf('Class "%s" doesn\'t exist or cannot be autoloaded. Check that the KERNEL_CLASS value in phpunit.xml matches the fully-qualified class name of your Kernel or override the "%s::createKernel()" method.', $class, static::class)); + } + + return $class; + } + + /** + * Boots the Kernel for this test. + */ + protected static function bootKernel(array $options = []): KernelInterface + { + static::ensureKernelShutdown(); + + $kernel = static::createKernel($options); + $kernel->boot(); + static::$kernel = $kernel; + static::$booted = true; + + return static::$kernel; + } + + /** + * Provides a dedicated test container with access to both public and private + * services. The container will not include private services that have been + * inlined or removed. Private services will be removed when they are not + * used by other services. + * + * Using this method is the best way to get a container from your test code. + * + * @return Container + */ + protected static function getContainer(): ContainerInterface + { + if (!static::$booted) { + static::bootKernel(); + } + + try { + return self::$kernel->getContainer()->get('test.service_container'); + } catch (ServiceNotFoundException $e) { + throw new \LogicException('Could not find service "test.service_container". Try updating the "framework.test" config to "true".', 0, $e); + } + } + + /** + * Creates a Kernel. + * + * Available options: + * + * * environment + * * debug + */ + protected static function createKernel(array $options = []): KernelInterface + { + static::$class ??= static::getKernelClass(); + + $env = $options['environment'] ?? $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'test'; + $debug = $options['debug'] ?? $_ENV['APP_DEBUG'] ?? $_SERVER['APP_DEBUG'] ?? true; + + return new static::$class($env, $debug); + } + + /** + * Shuts the kernel down if it was used in the test - called by the tearDown method by default. + */ + protected static function ensureKernelShutdown() + { + if (null !== static::$kernel) { + static::$kernel->boot(); + $container = static::$kernel->getContainer(); + static::$kernel->shutdown(); + static::$booted = false; + + if ($container instanceof ResetInterface) { + $container->reset(); + } + } + } +} diff --git a/lib/symfony/framework-bundle/Test/MailerAssertionsTrait.php b/lib/symfony/framework-bundle/Test/MailerAssertionsTrait.php new file mode 100644 index 000000000..83643421e --- /dev/null +++ b/lib/symfony/framework-bundle/Test/MailerAssertionsTrait.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\Event\MessageEvents; +use Symfony\Component\Mailer\Test\Constraint as MailerConstraint; +use Symfony\Component\Mime\RawMessage; +use Symfony\Component\Mime\Test\Constraint as MimeConstraint; + +trait MailerAssertionsTrait +{ + public static function assertEmailCount(int $count, string $transport = null, string $message = ''): void + { + self::assertThat(self::getMessageMailerEvents(), new MailerConstraint\EmailCount($count, $transport), $message); + } + + public static function assertQueuedEmailCount(int $count, string $transport = null, string $message = ''): void + { + self::assertThat(self::getMessageMailerEvents(), new MailerConstraint\EmailCount($count, $transport, true), $message); + } + + public static function assertEmailIsQueued(MessageEvent $event, string $message = ''): void + { + self::assertThat($event, new MailerConstraint\EmailIsQueued(), $message); + } + + public static function assertEmailIsNotQueued(MessageEvent $event, string $message = ''): void + { + self::assertThat($event, new LogicalNot(new MailerConstraint\EmailIsQueued()), $message); + } + + public static function assertEmailAttachmentCount(RawMessage $email, int $count, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailAttachmentCount($count), $message); + } + + public static function assertEmailTextBodyContains(RawMessage $email, string $text, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailTextBodyContains($text), $message); + } + + public static function assertEmailTextBodyNotContains(RawMessage $email, string $text, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailTextBodyContains($text)), $message); + } + + public static function assertEmailHtmlBodyContains(RawMessage $email, string $text, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailHtmlBodyContains($text), $message); + } + + public static function assertEmailHtmlBodyNotContains(RawMessage $email, string $text, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailHtmlBodyContains($text)), $message); + } + + public static function assertEmailHasHeader(RawMessage $email, string $headerName, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailHasHeader($headerName), $message); + } + + public static function assertEmailNotHasHeader(RawMessage $email, string $headerName, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailHasHeader($headerName)), $message); + } + + public static function assertEmailHeaderSame(RawMessage $email, string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailHeaderSame($headerName, $expectedValue), $message); + } + + public static function assertEmailHeaderNotSame(RawMessage $email, string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailHeaderSame($headerName, $expectedValue)), $message); + } + + public static function assertEmailAddressContains(RawMessage $email, string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailAddressContains($headerName, $expectedValue), $message); + } + + public static function assertEmailSubjectContains(RawMessage $email, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new MimeConstraint\EmailSubjectContains($expectedValue), $message); + } + + public static function assertEmailSubjectNotContains(RawMessage $email, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailSubjectContains($expectedValue)), $message); + } + + /** + * @return MessageEvent[] + */ + public static function getMailerEvents(string $transport = null): array + { + return self::getMessageMailerEvents()->getEvents($transport); + } + + public static function getMailerEvent(int $index = 0, string $transport = null): ?MessageEvent + { + return self::getMailerEvents($transport)[$index] ?? null; + } + + /** + * @return RawMessage[] + */ + public static function getMailerMessages(string $transport = null): array + { + return self::getMessageMailerEvents()->getMessages($transport); + } + + public static function getMailerMessage(int $index = 0, string $transport = null): ?RawMessage + { + return self::getMailerMessages($transport)[$index] ?? null; + } + + private static function getMessageMailerEvents(): MessageEvents + { + $container = static::getContainer(); + if ($container->has('mailer.message_logger_listener')) { + return $container->get('mailer.message_logger_listener')->getEvents(); + } + + static::fail('A client must have Mailer enabled to make email assertions. Did you forget to require symfony/mailer?'); + } +} diff --git a/lib/symfony/framework-bundle/Test/NotificationAssertionsTrait.php b/lib/symfony/framework-bundle/Test/NotificationAssertionsTrait.php new file mode 100644 index 000000000..53d24cb12 --- /dev/null +++ b/lib/symfony/framework-bundle/Test/NotificationAssertionsTrait.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +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 as NotifierConstraint; + +/* + * @author Smaïne Milianni + */ +trait NotificationAssertionsTrait +{ + public static function assertNotificationCount(int $count, string $transportName = null, string $message = ''): void + { + self::assertThat(self::getNotificationEvents(), new NotifierConstraint\NotificationCount($count, $transportName), $message); + } + + public static function assertQueuedNotificationCount(int $count, string $transportName = null, string $message = ''): void + { + self::assertThat(self::getNotificationEvents(), new NotifierConstraint\NotificationCount($count, $transportName, true), $message); + } + + public static function assertNotificationIsQueued(MessageEvent $event, string $message = ''): void + { + self::assertThat($event, new NotifierConstraint\NotificationIsQueued(), $message); + } + + public static function assertNotificationIsNotQueued(MessageEvent $event, string $message = ''): void + { + self::assertThat($event, new LogicalNot(new NotifierConstraint\NotificationIsQueued()), $message); + } + + public static function assertNotificationSubjectContains(MessageInterface $notification, string $text, string $message = ''): void + { + self::assertThat($notification, new NotifierConstraint\NotificationSubjectContains($text), $message); + } + + public static function assertNotificationSubjectNotContains(MessageInterface $notification, string $text, string $message = ''): void + { + self::assertThat($notification, new LogicalNot(new NotifierConstraint\NotificationSubjectContains($text)), $message); + } + + public static function assertNotificationTransportIsEqual(MessageInterface $notification, string $transportName = null, string $message = ''): void + { + self::assertThat($notification, new NotifierConstraint\NotificationTransportIsEqual($transportName), $message); + } + + public static function assertNotificationTransportIsNotEqual(MessageInterface $notification, string $transportName = null, string $message = ''): void + { + self::assertThat($notification, new LogicalNot(new NotifierConstraint\NotificationTransportIsEqual($transportName)), $message); + } + + /** + * @return MessageEvent[] + */ + public static function getNotifierEvents(string $transportName = null): array + { + return self::getNotificationEvents()->getEvents($transportName); + } + + public static function getNotifierEvent(int $index = 0, string $transportName = null): ?MessageEvent + { + return self::getNotifierEvents($transportName)[$index] ?? null; + } + + /** + * @return MessageInterface[] + */ + public static function getNotifierMessages(string $transportName = null): array + { + return self::getNotificationEvents()->getMessages($transportName); + } + + public static function getNotifierMessage(int $index = 0, string $transportName = null): ?MessageInterface + { + return self::getNotifierMessages($transportName)[$index] ?? null; + } + + public static function getNotificationEvents(): NotificationEvents + { + $container = static::getContainer(); + if ($container->has('notifier.notification_logger_listener')) { + return $container->get('notifier.notification_logger_listener')->getEvents(); + } + + static::fail('A client must have Notifier enabled to make notifications assertions. Did you forget to require symfony/notifier?'); + } +} diff --git a/lib/symfony/framework-bundle/Test/TestBrowserToken.php b/lib/symfony/framework-bundle/Test/TestBrowserToken.php new file mode 100644 index 000000000..8bf365eb0 --- /dev/null +++ b/lib/symfony/framework-bundle/Test/TestBrowserToken.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * A very limited token that is used to login in tests using the KernelBrowser. + * + * @author Wouter de Jong + */ +class TestBrowserToken extends AbstractToken +{ + private string $firewallName; + + public function __construct(array $roles = [], UserInterface $user = null, string $firewallName = 'main') + { + parent::__construct($roles); + + if (null !== $user) { + $this->setUser($user); + } + + $this->firewallName = $firewallName; + } + + public function getFirewallName(): string + { + return $this->firewallName; + } + + public function getCredentials(): mixed + { + return null; + } + + public function __serialize(): array + { + return [$this->firewallName, parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + [$this->firewallName, $parentData] = $data; + + parent::__unserialize($parentData); + } +} diff --git a/lib/symfony/framework-bundle/Test/TestContainer.php b/lib/symfony/framework-bundle/Test/TestContainer.php new file mode 100644 index 000000000..e1e7a8592 --- /dev/null +++ b/lib/symfony/framework-bundle/Test/TestContainer.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * A special container used in tests. This gives access to both public and + * private services. The container will not include private services that have + * been inlined or removed. Private services will be removed when they are not + * used by other services. + * + * @author Nicolas Grekas + * + * @internal + */ +class TestContainer extends Container +{ + public function __construct( + private KernelInterface $kernel, + private string $privateServicesLocatorId, + private array $renamedIds = [], + ) { + } + + public function compile(): void + { + $this->getPublicContainer()->compile(); + } + + public function isCompiled(): bool + { + return $this->getPublicContainer()->isCompiled(); + } + + public function getParameterBag(): ParameterBagInterface + { + return $this->getPublicContainer()->getParameterBag(); + } + + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + return $this->getPublicContainer()->getParameter($name); + } + + public function hasParameter(string $name): bool + { + return $this->getPublicContainer()->hasParameter($name); + } + + public function setParameter(string $name, mixed $value): void + { + $this->getPublicContainer()->setParameter($name, $value); + } + + public function set(string $id, mixed $service): void + { + $container = $this->getPublicContainer(); + $renamedId = $this->renamedIds[$id] ?? $id; + + try { + $container->set($renamedId, $service); + } catch (InvalidArgumentException $e) { + if (!str_starts_with($e->getMessage(), "The \"$renamedId\" service is private")) { + throw $e; + } + if (isset($container->privates[$renamedId])) { + throw new InvalidArgumentException(sprintf('The "%s" service is already initialized, you cannot replace it.', $id)); + } + $container->privates[$renamedId] = $service; + } + } + + public function has(string $id): bool + { + return $this->getPublicContainer()->has($id) || $this->getPrivateContainer()->has($id); + } + + public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE): ?object + { + return $this->getPrivateContainer()->has($id) ? $this->getPrivateContainer()->get($id) : $this->getPublicContainer()->get($id, $invalidBehavior); + } + + public function initialized(string $id): bool + { + return $this->getPublicContainer()->initialized($id); + } + + public function reset(): void + { + // ignore the call + } + + public function getServiceIds(): array + { + return $this->getPublicContainer()->getServiceIds(); + } + + public function getRemovedIds(): array + { + return $this->getPublicContainer()->getRemovedIds(); + } + + private function getPublicContainer(): Container + { + return $this->kernel->getContainer() ?? throw new \LogicException('Cannot access the container on a non-booted kernel. Did you forget to boot it?'); + } + + private function getPrivateContainer(): ContainerInterface + { + return $this->getPublicContainer()->get($this->privateServicesLocatorId); + } +} diff --git a/lib/symfony/framework-bundle/Test/WebTestAssertionsTrait.php b/lib/symfony/framework-bundle/Test/WebTestAssertionsTrait.php new file mode 100644 index 000000000..aebd4577b --- /dev/null +++ b/lib/symfony/framework-bundle/Test/WebTestAssertionsTrait.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +trait WebTestAssertionsTrait +{ + use BrowserKitAssertionsTrait; + use DomCrawlerAssertionsTrait; + use HttpClientAssertionsTrait; +} diff --git a/lib/symfony/framework-bundle/Test/WebTestCase.php b/lib/symfony/framework-bundle/Test/WebTestCase.php new file mode 100644 index 000000000..de31d4ba9 --- /dev/null +++ b/lib/symfony/framework-bundle/Test/WebTestCase.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; + +/** + * WebTestCase is the base class for functional tests. + * + * @author Fabien Potencier + */ +abstract class WebTestCase extends KernelTestCase +{ + use WebTestAssertionsTrait; + + protected function tearDown(): void + { + parent::tearDown(); + self::getClient(null); + } + + /** + * Creates a KernelBrowser. + * + * @param array $options An array of options to pass to the createKernel method + * @param array $server An array of server parameters + */ + protected static function createClient(array $options = [], array $server = []): KernelBrowser + { + if (static::$booted) { + throw new \LogicException(sprintf('Booting the kernel before calling "%s()" is not supported, the kernel should only be booted once.', __METHOD__)); + } + + $kernel = static::bootKernel($options); + + try { + $client = $kernel->getContainer()->get('test.client'); + } catch (ServiceNotFoundException) { + if (class_exists(KernelBrowser::class)) { + throw new \LogicException('You cannot create the client used in functional tests if the "framework.test" config is not set to true.'); + } + throw new \LogicException('You cannot create the client used in functional tests if the BrowserKit component is not available. Try running "composer require symfony/browser-kit".'); + } + + $client->setServerParameters($server); + + return self::getClient($client); + } +} diff --git a/sources/Dependencies/Composer/iTopComposer.php b/sources/Dependencies/Composer/iTopComposer.php index 4db223284..2578b06ad 100644 --- a/sources/Dependencies/Composer/iTopComposer.php +++ b/sources/Dependencies/Composer/iTopComposer.php @@ -40,6 +40,7 @@ class iTopComposer extends AbstractFolderAnalyzer { return [ 'twig/twig/src/Node/Expression/Test', + 'symfony/framework-bundle/Test', // Tools for testing Symfony applications ]; } @@ -87,7 +88,6 @@ class iTopComposer extends AbstractFolderAnalyzer 'symfony/http-foundation/Test', 'symfony/http-kernel/Tests', 'symfony/service-contracts/Test', - 'symfony/framework-bundle/Test', 'symfony/mime/Test', 'symfony/routing/Tests', 'symfony/stopwatch/Tests', diff --git a/tests/php-unit-tests/module_integration.xml.dist b/tests/php-unit-tests/module_integration.xml.dist index ad7ff7a99..f13e34082 100644 --- a/tests/php-unit-tests/module_integration.xml.dist +++ b/tests/php-unit-tests/module_integration.xml.dist @@ -2,7 +2,7 @@ LoadRequiredItopFiles(); $this->LoadRequiredTestFiles(); } @@ -212,7 +220,7 @@ abstract class ItopTestCase extends TestCase protected function LoadRequiredItopFiles(): void { // At least make sure that the autoloader will be loaded, and that the APPROOT constant is defined - require_once __DIR__.'/../../../../approot.inc.php'; + require_once __DIR__.'/../../../../approot.inc.php'; } /** @@ -541,4 +549,27 @@ abstract class ItopTestCase extends TestCase $this->AssertArraysHaveSameItems($aExpected, $aFiles, $sMessage); } + + /** + * Control which Kernel will be loaded when invoking the bootKernel method + * + * @see static::bootKernel(), static::getContainer() + * @see \Combodo\iTop\Kernel, \Combodo\iTop\Portal\Kernel + * + * @param string $sKernelClass + * + * @since 3.2.1 + */ + static protected function SetKernelClass(string $sKernelClass): void + { + $_SERVER['KERNEL_CLASS'] = $sKernelClass; + } + + static protected function bootKernel(array $options = []): KernelInterface + { + if (!array_key_exists('KERNEL_CLASS', $_SERVER)) { + throw new \LogicException('static::SetKernelClass() must be called before booting the kernel.'); + } + return parent::bootKernel($options); + } } diff --git a/tests/php-unit-tests/unitary-tests/tests/php-unit-tests/BootSymfonyKernelTest.php b/tests/php-unit-tests/unitary-tests/tests/php-unit-tests/BootSymfonyKernelTest.php new file mode 100644 index 000000000..9d3dc8e33 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/tests/php-unit-tests/BootSymfonyKernelTest.php @@ -0,0 +1,49 @@ +SetKernelClass(\Combodo\iTop\Portal\Kernel::class); + self::bootKernel(); + $controller = static::getContainer()->get(AggregatePageBrickController::class); + + $this->assertInstanceOf(AggregatePageBrickController::class, $controller); + } + + public function testInstantiateServiceWithAnAutomaticKernelBoot() + { + $this->SetKernelClass(\Combodo\iTop\Portal\Kernel::class); + $controller = static::getContainer()->get(AggregatePageBrickController::class); + + $this->assertInstanceOf(AggregatePageBrickController::class, $controller); + } + + public function testUnspecifiedKernelClassThrowsAnException() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('static::SetKernelClass() must be called before booting the kernel'); + + static::getContainer(); + } + + public function testTwoDifferentKernelsCanBeStartedConsecutively() + { + self::markTestSkipped('This test is still failing: the second kernel container does not find the requested service'); + + $this->SetKernelClass(\Combodo\iTop\Kernel::class); + self::bootKernel(); + + $this->SetKernelClass(\Combodo\iTop\Portal\Kernel::class); + self::bootKernel(); + $controller = static::getContainer()->get(AggregatePageBrickController::class); + + $this->assertInstanceOf(AggregatePageBrickController::class, $controller); + } +} diff --git a/tests/php-unit-tests/unittestautoload.php b/tests/php-unit-tests/unittestautoload.php index 8b720f250..73ec676c8 100644 --- a/tests/php-unit-tests/unittestautoload.php +++ b/tests/php-unit-tests/unittestautoload.php @@ -2,6 +2,8 @@ // Main autoload, this is the one to use in the PHPUnit configuration // -// It was previously used to include both the vendor autoloader and our custom base test case classes, but these are now autoloaded from ./src/BasetestCase -// This file should then no longer be necessary, but we have to keep it until projects / branches / modules have been corrected. +// This file was previously mentioned as deprecated, and now it HAS to be used (see phpunit.xml/bootstrap attribute) require_once 'vendor/autoload.php'; + +// Required to benefit from symfony/framework-bundle's KernelTestCase, which is in a package which is a mix of runtime and test tools +require_once __DIR__.'/../../lib/autoload.php';