diff --git a/features/mink_integration/accessing_drivers_service_container.feature b/features/mink_integration/accessing_drivers_service_container.feature new file mode 100644 index 0000000..44bb5c2 --- /dev/null +++ b/features/mink_integration/accessing_drivers_service_container.feature @@ -0,0 +1,98 @@ +Feature: Accessing driver's service container + + Background: + Given a working Symfony application with SymfonyExtension configured + And a Behat configuration containing: + """ + default: + extensions: + Behat\MinkExtension: + base_url: "http://localhost:8080/" + default_session: symfony + sessions: + symfony: + symfony: ~ + suites: + default: + contexts: + - App\Tests\SomeContext + """ + And a feature file containing: + """ + Feature: + Scenario: + Given the counter service is zeroed + When I visit the page "/hello-world" + Then the counter service should return 1 + """ + And a context file "tests/SomeContext.php" containing: + """ + mink = $mink; + $this->driverContainer = $driverContainer; + } + + /** @Given the counter service is zeroed */ + public function counterServiceIsZeroed(): void + { + assert(0 === $this->getCounterService()->get()); + } + + /** @When I visit the page :page */ + public function visitPage(string $page): void + { + $this->mink->getSession()->visit($page); + } + + /** @Then the counter service should return :number */ + public function counterServiceShouldReturn(int $number): void + { + assert($number === $this->getCounterService()->get()); + } + + private function getCounterService(): Counter + { + return $this->driverContainer->get('App\Counter'); + } + } + """ + + Scenario: Accessing a service from driver's service container (manually injected dependencies) + Given a YAML services file containing: + """ + services: + App\Tests\SomeContext: + public: true + arguments: + - '@behat.mink' + - '@behat.driver.service_container' + """ + When I run Behat + Then it should pass + + Scenario: Accessing a service from driver's service container (autowired & autoconfigured dependencies) + Given a YAML services file containing: + """ + services: + _defaults: + autowire: true + autoconfigure: true + + App\Tests\SomeContext: ~ + """ + When I run Behat + Then it should pass diff --git a/src/Bundle/DependencyInjection/FriendsOfBehatSymfonyExtensionExtension.php b/src/Bundle/DependencyInjection/FriendsOfBehatSymfonyExtensionExtension.php index abfd109..cb8c4ff 100644 --- a/src/Bundle/DependencyInjection/FriendsOfBehatSymfonyExtensionExtension.php +++ b/src/Bundle/DependencyInjection/FriendsOfBehatSymfonyExtensionExtension.php @@ -8,6 +8,7 @@ use Behat\Mink\Mink; use Behat\Mink\Session; use FriendsOfBehat\SymfonyExtension\Mink\MinkParameters; +use FriendsOfBehat\SymfonyExtension\ServiceContainer\SymfonyExtension; use Symfony\Component\BrowserKit\Client; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -15,6 +16,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\HttpKernel\KernelInterface; final class FriendsOfBehatSymfonyExtensionExtension extends Extension implements CompilerPassInterface { @@ -22,6 +24,7 @@ public function load(array $configs, ContainerBuilder $container): void { $this->provideMinkIntegration($container); $this->registerBehatContainer($container); + $this->registerDriverBehatContainer($container); $container->registerForAutoconfiguration(Context::class)->addTag('fob.context'); } @@ -47,6 +50,27 @@ private function registerBehatContainer(ContainerBuilder $container): void $container->setDefinition('behat.service_container', $behatServiceContainerDefinition); } + private function registerDriverBehatContainer(ContainerBuilder $container): void + { + $driverKernelDefinition = new Definition(KernelInterface::class, [SymfonyExtension::DRIVER_KERNEL_ID]); + $driverKernelDefinition->setFactory([new Reference('behat.service_container'), 'get']); + $driverKernelDefinition->setPublic(true); + $driverKernelDefinition->setLazy(true); + + $driverServiceContainerDefinition = new Definition(ContainerInterface::class); + $driverServiceContainerDefinition->setFactory([$driverKernelDefinition, 'getContainer']); + $driverServiceContainerDefinition->setPublic(true); + $driverServiceContainerDefinition->setLazy(true); + + $driverTestServiceContainerDefinition = new Definition(ContainerInterface::class, ['test.service_container']); + $driverTestServiceContainerDefinition->setFactory([$driverServiceContainerDefinition, 'get']); + $driverTestServiceContainerDefinition->setPublic(true); + $driverTestServiceContainerDefinition->setLazy(true); + + $container->setDefinition('behat.driver.service_container', $driverTestServiceContainerDefinition); + $container->registerAliasForArgument('behat.driver.service_container', ContainerInterface::class, 'driver container'); + } + private function provideBrowserKitIntegration(ContainerBuilder $container): void { if (!class_exists(Client::class) || !$container->has('test.client')) { diff --git a/src/ServiceContainer/SymfonyExtension.php b/src/ServiceContainer/SymfonyExtension.php index f0d920c..d8e0565 100644 --- a/src/ServiceContainer/SymfonyExtension.php +++ b/src/ServiceContainer/SymfonyExtension.php @@ -34,7 +34,7 @@ final class SymfonyExtension implements Extension * Kernel used by Symfony driver to isolate web container from contexts' container. * Container is rebuilt before every request. */ - private const DRIVER_KERNEL_ID = 'fob_symfony.driver_kernel'; + public const DRIVER_KERNEL_ID = 'fob_symfony.driver_kernel'; /** @var bool */ private $minkExtensionFound = false; diff --git a/tests/Behat/Context/TestContext.php b/tests/Behat/Context/TestContext.php index a7f56fc..5c2ed64 100644 --- a/tests/Behat/Context/TestContext.php +++ b/tests/Behat/Context/TestContext.php @@ -92,12 +92,13 @@ class: App\Kernel $this->thereIsFile('src/Kernel.php', <<<'CON' 'Pigeon', ]); + $loader->load(__DIR__ . '/../config/default.yaml'); $loader->load(__DIR__ . '/../config/services.yaml'); } - protected function configureRoutes(RouteCollectionBuilder $routes) + protected function configureRoutes(RouteCollectionBuilder $routes): void + { + $routes->add('/hello-world', 'App\Controller:helloWorld'); + } +} +CON + ); + + $this->thereIsFile('src/Controller.php', <<<'CON' +counter = $counter; + } + + public function helloWorld(): Response + { + $this->counter->increase(); + + return new Response('Hello world!'); + } +} +CON + ); + + $this->thereIsFile('src/Counter.php', <<<'CON' +add('/hello-world', 'kernel:helloWorld'); + $this->counter++; + } + + public function get(): int + { + return $this->counter; } } CON ); + $this->thereIsFile('config/default.yaml', <<<'YML' +services: + App\Controller: + arguments: + - '@App\Counter' + public: true + + App\Counter: + public: false +YML + ); + $this->thereIsFile('config/services.yaml', ''); }