diff --git a/.gitignore b/.gitignore index 7579f74..4a45ef5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ vendor composer.lock +.php_cs.cache diff --git a/argumentsExt/Annotation/StepInjectorArgument.php b/argumentsExt/Annotation/StepInjectorArgument.php new file mode 100644 index 0000000..3e19fbe --- /dev/null +++ b/argumentsExt/Annotation/StepInjectorArgument.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gorghoa\StepArgumentInjectorBehatExtension\Annotation; + +/** + * @author Rodrigue Villetard + */ +interface StepInjectorArgument +{ + /** + * The argument name this annotation is targeting to inject. + * + * @return string + */ + public function getArgument(); +} diff --git a/src/Argument/ScenarioStateArgumentOrganiser.php b/argumentsExt/Argument/ArgumentOrganiser.php similarity index 50% rename from src/Argument/ScenarioStateArgumentOrganiser.php rename to argumentsExt/Argument/ArgumentOrganiser.php index 172652a..34aba7e 100644 --- a/src/Argument/ScenarioStateArgumentOrganiser.php +++ b/argumentsExt/Argument/ArgumentOrganiser.php @@ -1,7 +1,7 @@ * @@ -9,39 +9,39 @@ * file that was distributed with this source code. */ -namespace Gorghoa\ScenarioStateBehatExtension\Argument; +namespace Gorghoa\StepArgumentInjectorBehatExtension\Argument; -use Behat\Testwork\Argument\ArgumentOrganiser; +use Behat\Testwork\Argument\ArgumentOrganiser as BehatArgumentOrganiser; use Doctrine\Common\Annotations\Reader; -use Gorghoa\ScenarioStateBehatExtension\Annotation\ScenarioStateArgument; -use Gorghoa\ScenarioStateBehatExtension\Context\Initializer\ScenarioStateInitializer; +use Gorghoa\StepArgumentInjectorBehatExtension\Annotation\StepInjectorArgument; +// use Gorghoa\StepArgumentInjectorBehatExtension\Context\Initializer\StepArgumentInjectorInitializer; use ReflectionFunctionAbstract; /** * @author Rodrigue Villetard * @author Vincent Chalamon */ -final class ScenarioStateArgumentOrganiser implements ArgumentOrganiser +final class ArgumentOrganiser implements BehatArgumentOrganiser { /** - * @var ArgumentOrganiser + * @var BehatArgumentOrganiser */ private $baseOrganiser; /** - * @var ScenarioStateInitializer + * @var StepArgumentHolder[] */ - private $store; + private $stepArgumentHolders; /** * @var Reader */ private $reader; - public function __construct(ArgumentOrganiser $organiser, ScenarioStateInitializer $store, Reader $reader) + public function __construct(BehatArgumentOrganiser $organiser, array $stepArgumentHolders, Reader $reader) { $this->baseOrganiser = $organiser; - $this->store = $store; + $this->stepArgumentHolders = $stepArgumentHolders; $this->reader = $reader; } @@ -59,16 +59,22 @@ public function organiseArguments(ReflectionFunctionAbstract $function, array $m return $this->baseOrganiser->organiseArguments($function, $match); } - /** @var ScenarioStateArgument[] $annotations */ $annotations = $this->reader->getMethodAnnotations($function); - $store = $this->store->getStore(); + foreach ($annotations as $annotation) { - if ($annotation instanceof ScenarioStateArgument && - in_array($annotation->getArgument(), $paramsKeys) && - $store->hasStateFragment($annotation->getName()) + if ($annotation instanceof StepInjectorArgument && + in_array($argument = $annotation->getArgument(), $paramsKeys) ) { - $match[$annotation->getArgument()] = $store->getStateFragment($annotation->getName()); - $match[strval(++$i)] = $store->getStateFragment($annotation->getName()); + /* @var StepInjectorArgument $annotation */ + foreach ($this->stepArgumentHolders as $hooker) { + if ($hooker->doesHandleStepArgument($annotation)) { + + $match[$argument] + = $match[strval(++$i)] + = $hooker->getStepArgumentValueFor($annotation) + ; + } + } } } diff --git a/argumentsExt/Argument/StepArgumentHolder.php b/argumentsExt/Argument/StepArgumentHolder.php new file mode 100644 index 0000000..a683c58 --- /dev/null +++ b/argumentsExt/Argument/StepArgumentHolder.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gorghoa\StepArgumentInjectorBehatExtension\Argument; + +use Gorghoa\StepArgumentInjectorBehatExtension\Annotation\StepInjectorArgument; + +/** + * @author Rodrigue Villetard + */ +interface StepArgumentHolder +{ + /** + * Check if an annotation is handled by the service. + * + * @param StepInjectorArgument $annotation + * + * @return bool + */ + public function doesHandleStepArgument(StepInjectorArgument $annotation); + + /** + * Get value to inject for a step argument. + * + * @param StepInjectorArgument $annotation + * + * @return mixed + */ + public function getStepArgumentValueFor(StepInjectorArgument $annotation); +} diff --git a/src/Call/Handler/RuntimeCallHandler.php b/argumentsExt/Call/Handler/RuntimeCallHandler.php similarity index 91% rename from src/Call/Handler/RuntimeCallHandler.php rename to argumentsExt/Call/Handler/RuntimeCallHandler.php index 9a8cb75..a60b3cf 100644 --- a/src/Call/Handler/RuntimeCallHandler.php +++ b/argumentsExt/Call/Handler/RuntimeCallHandler.php @@ -1,7 +1,7 @@ * @@ -9,14 +9,14 @@ * file that was distributed with this source code. */ -namespace Gorghoa\ScenarioStateBehatExtension\Call\Handler; +namespace Gorghoa\StepArgumentInjectorBehatExtension\Call\Handler; use Behat\Behat\Transformation\Call\TransformationCall; use Behat\Testwork\Call\Call; use Behat\Testwork\Call\Handler\CallHandler; use Behat\Testwork\Environment\Call\EnvironmentCall; use Behat\Testwork\Hook\Call\HookCall; -use Gorghoa\ScenarioStateBehatExtension\Resolver\ArgumentsResolver; +use Gorghoa\StepArgumentInjectorBehatExtension\Resolver\ArgumentsResolver; /** * @author Vincent Chalamon diff --git a/argumentsExt/Exception/RejectedAnnotationException.php b/argumentsExt/Exception/RejectedAnnotationException.php new file mode 100644 index 0000000..4e926ce --- /dev/null +++ b/argumentsExt/Exception/RejectedAnnotationException.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 Gorghoa\StepArgumentInjectorBehatExtension\Exception; + +/** + * @author Rodrigue Villetard + */ +class RejectedAnnotationException extends \LogicException +{ +} diff --git a/src/Resolver/ArgumentsResolver.php b/argumentsExt/Resolver/ArgumentsResolver.php similarity index 56% rename from src/Resolver/ArgumentsResolver.php rename to argumentsExt/Resolver/ArgumentsResolver.php index d25e4bd..994f896 100644 --- a/src/Resolver/ArgumentsResolver.php +++ b/argumentsExt/Resolver/ArgumentsResolver.php @@ -1,7 +1,7 @@ * @@ -9,11 +9,10 @@ * file that was distributed with this source code. */ -namespace Gorghoa\ScenarioStateBehatExtension\Resolver; +namespace Gorghoa\StepArgumentInjectorBehatExtension\Resolver; use Doctrine\Common\Annotations\Reader; -use Gorghoa\ScenarioStateBehatExtension\Annotation\ScenarioStateArgument; -use Gorghoa\ScenarioStateBehatExtension\Context\Initializer\ScenarioStateInitializer; +use Gorghoa\StepArgumentInjectorBehatExtension\Annotation\StepInjectorArgument; /** * @author Vincent Chalamon @@ -21,22 +20,24 @@ class ArgumentsResolver { /** - * @var ScenarioStateInitializer + * @var Reader */ - private $store; + private $reader; /** - * @var Reader + * @var StepArgumentHolder[] */ - private $reader; + private $stepArgumentHolders; /** - * @param ScenarioStateInitializer $store - * @param Reader $reader + * ArgumentsResolver constructor. + * + * @param StepArgumentHolder[] $stepArgumentHolders + * @param Reader $reader */ - public function __construct(ScenarioStateInitializer $store, Reader $reader) + public function __construct($stepArgumentHolders, Reader $reader) { - $this->store = $store; + $this->stepArgumentHolders = $stepArgumentHolders; $this->reader = $reader; } @@ -48,25 +49,27 @@ public function __construct(ScenarioStateInitializer $store, Reader $reader) */ public function resolve(\ReflectionMethod $function, array $arguments) { - // No `@ScenarioStateArgument` annotation found - if (null === $this->reader->getMethodAnnotation($function, ScenarioStateArgument::class)) { + // No `@StepInjectorArgument` annotation found + if (null === $this->reader->getMethodAnnotation($function, StepInjectorArgument::class)) { return $arguments; } $paramsKeys = array_map(function (\ReflectionParameter $element) { return $element->getName(); }, $function->getParameters()); - $store = $this->store->getStore(); // Prepare arguments from annotations - /** @var ScenarioStateArgument[] $annotations */ $annotations = $this->reader->getMethodAnnotations($function); foreach ($annotations as $annotation) { - if ($annotation instanceof ScenarioStateArgument && - in_array($annotation->getArgument(), $paramsKeys) && - $store->hasStateFragment($annotation->getName()) + if ($annotation instanceof StepInjectorArgument && + in_array($argument = $annotation->getArgument(), $paramsKeys) ) { - $arguments[$annotation->getArgument()] = $store->getStateFragment($annotation->getName()); + /* @var StepArgumentInjectorArgument $annotation */ + foreach ($this->stepArgumentHolders as $hooker) { + if ($hooker->doesHandleStepArgument($annotation)) { + $arguments[$argument] = $hooker->getStepArgumentValueFor($annotation); + } + } } } diff --git a/argumentsExt/ServiceContainer/StepArgumentInjectorExtension.php b/argumentsExt/ServiceContainer/StepArgumentInjectorExtension.php new file mode 100644 index 0000000..ff65b4e --- /dev/null +++ b/argumentsExt/ServiceContainer/StepArgumentInjectorExtension.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gorghoa\StepArgumentInjectorBehatExtension\ServiceContainer; + +use Behat\Testwork\Argument\ServiceContainer\ArgumentExtension; +use Behat\Testwork\Call\ServiceContainer\CallExtension; +use Behat\Testwork\ServiceContainer\Extension as ExtensionInterface; +use Behat\Testwork\ServiceContainer\ExtensionManager; +use Doctrine\Common\Annotations\AnnotationReader; +use Gorghoa\StepArgumentInjectorBehatExtension\Argument\ArgumentOrganiser; +use Gorghoa\StepArgumentInjectorBehatExtension\Call\Handler\RuntimeCallHandler; +use Gorghoa\StepArgumentInjectorBehatExtension\Resolver\ArgumentsResolver; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Behat store for Behat contexts. + * + * @author Rodrigue Villetard + * @author Vincent Chalamon + */ +class StepArgumentInjectorExtension implements ExtensionInterface +{ + const STEP_ARGUMENT_INJECTOR_ARGUMENT_ORGANISER_ID = 'argument.step_argument_injector.organiser'; + const STEP_ARGUMENT_INJECTOR_DISPATCHER_ID = 'hook.step_argument_injector.dispatcher'; + const STEP_ARGUMENT_INJECTOR_TESTER_ID = 'tester.step_argument_injector.wrapper'; + const STEP_ARGUMENT_INJECTOR_CALL_HANDLER_ID = 'call.step_argument_injector.call_handler'; + const STEP_ARGUMENT_INJECTOR_ARGUMENTS_RESOLVER_ID = 'step_argument_injector.arguments_resolver'; + const STEP_ARGUMENT_INJECTOR_STORE_ID = 'behatstore.context_initializer.store_aware'; + const STEP_ARGUMENT_INJECTOR_DOCTRINE_READER_ID = 'doctrine.reader.annotation'; + const STEP_ARGUMENT_INJECTOR_HOOK_TAG_ID = 'step_argument_injector.hook_tag_id'; + + /** + * {@inheritdoc} + */ + public function getConfigKey() + { + return 'stepargumentinjector'; + } + + /** + * {@inheritdoc} + */ + public function initialize(ExtensionManager $extensionManager) + { + } + + /** + * {@inheritdoc} + */ + public function configure(ArrayNodeDefinition $builder) + { + } + + /** + * {@inheritdoc} + */ + public function load(ContainerBuilder $container, array $config) + { + // Declare Doctrine annotation reader as service + $container->register(self::STEP_ARGUMENT_INJECTOR_DOCTRINE_READER_ID, AnnotationReader::class) + // Ignore Behat annotations in reader + ->addMethodCall('addGlobalIgnoredName', ['Given']) + ->addMethodCall('addGlobalIgnoredName', ['When']) + ->addMethodCall('addGlobalIgnoredName', ['Then']) + ->addMethodCall('addGlobalIgnoredName', ['Transform']) + ->addMethodCall('addGlobalIgnoredName', ['BeforeStep']) + ->addMethodCall('addGlobalIgnoredName', ['BeforeScenario']) + ->addMethodCall('addGlobalIgnoredName', ['BeforeFeature']) + ->addMethodCall('addGlobalIgnoredName', ['BeforeSuite']) + ->addMethodCall('addGlobalIgnoredName', ['AfterStep']) + ->addMethodCall('addGlobalIgnoredName', ['AfterScenario']) + ->addMethodCall('addGlobalIgnoredName', ['AfterFeature']) + ->addMethodCall('addGlobalIgnoredName', ['AfterSuite']); + + $taggedServices = array_map(function ($serviceId) { + return new Reference($serviceId); + }, array_keys($container->findTaggedServiceIds(self::STEP_ARGUMENT_INJECTOR_HOOK_TAG_ID))); + + // Arguments resolver: resolve StepArgumentInjector arguments from annotation + $container->register(self::STEP_ARGUMENT_INJECTOR_ARGUMENTS_RESOLVER_ID, ArgumentsResolver::class) + ->setArguments([ + $taggedServices, + new Reference(self::STEP_ARGUMENT_INJECTOR_DOCTRINE_READER_ID), + ]); + + // Argument organiser + $container->register(self::STEP_ARGUMENT_INJECTOR_ARGUMENT_ORGANISER_ID, ArgumentOrganiser::class) + ->setDecoratedService(ArgumentExtension::PREG_MATCH_ARGUMENT_ORGANISER_ID) + ->setPublic(false) + ->setArguments([ + new Reference(sprintf('%s.inner', self::STEP_ARGUMENT_INJECTOR_ARGUMENT_ORGANISER_ID)), + $taggedServices, + new Reference(self::STEP_ARGUMENT_INJECTOR_DOCTRINE_READER_ID), + new Reference(self::STEP_ARGUMENT_INJECTOR_ARGUMENTS_RESOLVER_ID), + ]); + + // Override calls process + $container->register(self::STEP_ARGUMENT_INJECTOR_CALL_HANDLER_ID, RuntimeCallHandler::class) + ->setDecoratedService(CallExtension::CALL_HANDLER_TAG.'.runtime') + ->setArguments([ + new Reference(self::STEP_ARGUMENT_INJECTOR_CALL_HANDLER_ID.'.inner'), + new Reference(self::STEP_ARGUMENT_INJECTOR_ARGUMENTS_RESOLVER_ID), + ]); + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + } +} diff --git a/composer.json b/composer.json index 5e63c3c..cbaa576 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "autoload": { "psr-4": { + "Gorghoa\\StepArgumentInjectorBehatExtension\\": "argumentsExt/", "Gorghoa\\ScenarioStateBehatExtension\\": "src/", "Symfony\\Component\\Process\\": "vendor/symfony/process/" } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a62fc3b..b9de83c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -14,5 +14,8 @@ tests + + testsSAI + diff --git a/src/Annotation/ScenarioStateArgument.php b/src/Annotation/ScenarioStateArgument.php index b447264..a6f58cc 100644 --- a/src/Annotation/ScenarioStateArgument.php +++ b/src/Annotation/ScenarioStateArgument.php @@ -11,13 +11,15 @@ namespace Gorghoa\ScenarioStateBehatExtension\Annotation; +use Gorghoa\StepArgumentInjectorBehatExtension\Annotation\StepInjectorArgument; + /** * @author Vincent Chalamon * * @Annotation * @Target("METHOD") */ -class ScenarioStateArgument +class ScenarioStateArgument implements StepInjectorArgument { /** * Argument name in store. diff --git a/src/Context/Initializer/ScenarioStateInitializer.php b/src/Context/Initializer/ScenarioStateInitializer.php index 379a5d8..5259796 100644 --- a/src/Context/Initializer/ScenarioStateInitializer.php +++ b/src/Context/Initializer/ScenarioStateInitializer.php @@ -14,15 +14,19 @@ use Behat\Behat\Context\Context; use Behat\Behat\Context\Initializer\ContextInitializer; use Behat\Behat\EventDispatcher\Event\ScenarioTested; +use Gorghoa\ScenarioStateBehatExtension\Annotation\ScenarioStateArgument; use Gorghoa\ScenarioStateBehatExtension\Context\ScenarioStateAwareContext; use Gorghoa\ScenarioStateBehatExtension\ScenarioState; use Gorghoa\ScenarioStateBehatExtension\ScenarioStateInterface; +use Gorghoa\StepArgumentInjectorBehatExtension\Annotation\StepInjectorArgument; +use Gorghoa\StepArgumentInjectorBehatExtension\Argument\StepArgumentHolder; +use Gorghoa\StepArgumentInjectorBehatExtension\Exception\RejectedAnnotationException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * @author Rodrigue Villetard */ -class ScenarioStateInitializer implements ContextInitializer, EventSubscriberInterface +class ScenarioStateInitializer implements ContextInitializer, EventSubscriberInterface, StepArgumentHolder { /** * @var ScenarioStateInterface @@ -68,4 +72,35 @@ public function getStore() { return $this->store; } + + /** + * Test if this service should handle specific argument injection. + * + * @param StepInjectorArgument $annotation + * + * @return bool + */ + public function doesHandleStepArgument(StepInjectorArgument $annotation) + { + return $annotation instanceof ScenarioStateArgument && $this->store->hasStateFragment($annotation->getName()); + } + + /** + * Get an argument value to inject. + * + * @param StepInjectorArgument $annotation + * + * @throws RejectedAnnotationException + * + * @return mixed + */ + public function getStepArgumentValueFor(StepInjectorArgument $annotation) + { + if (!($annotation instanceof ScenarioStateArgument)) { + $class = get_class($annotation); + throw new RejectedAnnotationException("$class not handled by ScenarioStateBehatExtension"); + } + + return $this->store->getStateFragment($annotation->getName()); + } } diff --git a/src/ScenarioStateInterface.php b/src/ScenarioStateInterface.php index 780b51d..6c7733f 100644 --- a/src/ScenarioStateInterface.php +++ b/src/ScenarioStateInterface.php @@ -26,7 +26,9 @@ public function provideStateFragment($key, $value); /** * @param string $key + * * @throws MissingStateException + * * @return mixed */ public function getStateFragment($key); diff --git a/src/ServiceContainer/ScenarioStateExtension.php b/src/ServiceContainer/ScenarioStateExtension.php index 7bd85fa..ef15327 100644 --- a/src/ServiceContainer/ScenarioStateExtension.php +++ b/src/ServiceContainer/ScenarioStateExtension.php @@ -12,24 +12,14 @@ namespace Gorghoa\ScenarioStateBehatExtension\ServiceContainer; use Behat\Behat\Context\ServiceContainer\ContextExtension; -use Behat\Behat\Tester\ServiceContainer\TesterExtension; -use Behat\Testwork\Argument\ServiceContainer\ArgumentExtension; -use Behat\Testwork\Call\ServiceContainer\CallExtension; use Behat\Testwork\EventDispatcher\ServiceContainer\EventDispatcherExtension; -use Behat\Testwork\Hook\ServiceContainer\HookExtension; use Behat\Testwork\ServiceContainer\Extension as ExtensionInterface; use Behat\Testwork\ServiceContainer\ExtensionManager; -use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationRegistry; -use Gorghoa\ScenarioStateBehatExtension\Argument\ScenarioStateArgumentOrganiser; -use Gorghoa\ScenarioStateBehatExtension\Call\Handler\RuntimeCallHandler; use Gorghoa\ScenarioStateBehatExtension\Context\Initializer\ScenarioStateInitializer; -use Gorghoa\ScenarioStateBehatExtension\Hook\Dispatcher\ScenarioStateHookDispatcher; -use Gorghoa\ScenarioStateBehatExtension\Hook\Tester\ScenarioStateHookableScenarioTester; -use Gorghoa\ScenarioStateBehatExtension\Resolver\ArgumentsResolver; +use Gorghoa\StepArgumentInjectorBehatExtension\ServiceContainer\StepArgumentInjectorExtension; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; /** * Behat store for Behat contexts. @@ -39,13 +29,8 @@ */ class ScenarioStateExtension implements ExtensionInterface { - const SCENARIO_STATE_ARGUMENT_ORGANISER_ID = 'argument.scenario_state.organiser'; - const SCENARIO_STATE_DISPATCHER_ID = 'hook.scenario_state.dispatcher'; - const SCENARIO_STATE_TESTER_ID = 'tester.scenario_state.wrapper'; - const SCENARIO_STATE_CALL_HANDLER_ID = 'call.scenario_state.call_handler'; - const SCENARIO_STATE_ARGUMENTS_RESOLVER_ID = 'scenario_state.arguments_resolver'; const SCENARIO_STATE_STORE_ID = 'behatstore.context_initializer.store_aware'; - const SCENARIO_STATE_DOCTRINE_READER_ID = 'doctrine.reader.annotation'; + const SCENARIO_STATE_ARGUMENT_INJECTOR_STORE_ID = 'behatstore.context_initializer.store_aware'; /** * {@inheritdoc} @@ -78,49 +63,9 @@ public function load(ContainerBuilder $container, array $config) // Load ScenarioState store $container->register(self::SCENARIO_STATE_STORE_ID, ScenarioStateInitializer::class) ->addTag(ContextExtension::INITIALIZER_TAG, ['priority' => 0]) - ->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, ['priority' => 0]); - - // Declare Doctrine annotation reader as service - $container->register(self::SCENARIO_STATE_DOCTRINE_READER_ID, AnnotationReader::class) - // Ignore Behat annotations in reader - ->addMethodCall('addGlobalIgnoredName', ['Given']) - ->addMethodCall('addGlobalIgnoredName', ['When']) - ->addMethodCall('addGlobalIgnoredName', ['Then']) - ->addMethodCall('addGlobalIgnoredName', ['Transform']) - ->addMethodCall('addGlobalIgnoredName', ['BeforeStep']) - ->addMethodCall('addGlobalIgnoredName', ['BeforeScenario']) - ->addMethodCall('addGlobalIgnoredName', ['BeforeFeature']) - ->addMethodCall('addGlobalIgnoredName', ['BeforeSuite']) - ->addMethodCall('addGlobalIgnoredName', ['AfterStep']) - ->addMethodCall('addGlobalIgnoredName', ['AfterScenario']) - ->addMethodCall('addGlobalIgnoredName', ['AfterFeature']) - ->addMethodCall('addGlobalIgnoredName', ['AfterSuite']); - - // Arguments resolver: resolve ScenarioState arguments from annotation - $container->register(self::SCENARIO_STATE_ARGUMENTS_RESOLVER_ID, ArgumentsResolver::class) - ->setArguments([ - new Reference(self::SCENARIO_STATE_STORE_ID), - new Reference(self::SCENARIO_STATE_DOCTRINE_READER_ID), - ]); - - // Argument organiser - $container->register(self::SCENARIO_STATE_ARGUMENT_ORGANISER_ID, ScenarioStateArgumentOrganiser::class) - ->setDecoratedService(ArgumentExtension::PREG_MATCH_ARGUMENT_ORGANISER_ID) - ->setPublic(false) - ->setArguments([ - new Reference(sprintf('%s.inner', self::SCENARIO_STATE_ARGUMENT_ORGANISER_ID)), - new Reference(self::SCENARIO_STATE_STORE_ID), - new Reference(self::SCENARIO_STATE_DOCTRINE_READER_ID), - new Reference(self::SCENARIO_STATE_ARGUMENTS_RESOLVER_ID), - ]); - - // Override calls process - $container->register(self::SCENARIO_STATE_CALL_HANDLER_ID, RuntimeCallHandler::class) - ->setDecoratedService(CallExtension::CALL_HANDLER_TAG.'.runtime') - ->setArguments([ - new Reference(self::SCENARIO_STATE_CALL_HANDLER_ID.'.inner'), - new Reference(self::SCENARIO_STATE_ARGUMENTS_RESOLVER_ID), - ]); + ->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, ['priority' => 0]) + ->addTag(StepArgumentInjectorExtension::STEP_ARGUMENT_INJECTOR_HOOK_TAG_ID, ['priority' => 0]) + ; } /** diff --git a/testapp/behat.yml b/testapp/behat.yml index efdd3fc..9ca9379 100644 --- a/testapp/behat.yml +++ b/testapp/behat.yml @@ -1,3 +1,4 @@ default: extensions: Gorghoa\ScenarioStateBehatExtension\ServiceContainer\ScenarioStateExtension: ~ + Gorghoa\StepArgumentInjectorBehatExtension\ServiceContainer\StepArgumentInjectorExtension: ~ diff --git a/tests/Argument/ScenarioStateArgumentOrganiserTest.php b/tests/Argument/ScenarioStateArgumentOrganiserTest.php deleted file mode 100644 index a5a30e9..0000000 --- a/tests/Argument/ScenarioStateArgumentOrganiserTest.php +++ /dev/null @@ -1,114 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Gorghoa\ScenarioStateBehatExtension\Argument; - -use Behat\Testwork\Argument\ArgumentOrganiser; -use Doctrine\Common\Annotations\Reader; -use Gorghoa\ScenarioStateBehatExtension\Annotation\ScenarioStateArgument; -use Gorghoa\ScenarioStateBehatExtension\Context\Initializer\ScenarioStateInitializer; -use Gorghoa\ScenarioStateBehatExtension\ScenarioStateInterface; -use Prophecy\Prophecy\ObjectProphecy; - -/** - * @author Vincent Chalamon - */ -class ScenarioStateArgumentOrganiserTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var ScenarioStateArgumentOrganiser - */ - private $organiser; - - /** - * @var ObjectProphecy|ArgumentOrganiser - */ - private $organiserMock; - - /** - * @var ObjectProphecy|ScenarioStateInitializer - */ - private $initializerMock; - - /** - * @var ObjectProphecy|ScenarioStateInterface - */ - private $storeMock; - - /** - * @var ObjectProphecy|\ReflectionMethod - */ - private $functionMock; - - /** - * @var ObjectProphecy|Reader - */ - private $readerMock; - - /** - * @var ObjectProphecy|ScenarioStateArgument - */ - private $annotationMock; - - protected function setUp() - { - $this->organiserMock = $this->prophesize(ArgumentOrganiser::class); - $this->initializerMock = $this->prophesize(ScenarioStateInitializer::class); - $this->storeMock = $this->prophesize(ScenarioStateInterface::class); - $this->functionMock = $this->prophesize(\ReflectionMethod::class); - $this->readerMock = $this->prophesize(Reader::class); - $this->annotationMock = $this->prophesize(ScenarioStateArgument::class); - - $this->organiser = new ScenarioStateArgumentOrganiser( - $this->organiserMock->reveal(), - $this->initializerMock->reveal(), - $this->readerMock->reveal() - ); - } - - public function testOrganiseArguments() - { - $this->functionMock->getParameters()->willReturn([ - (object) ['name' => 'scenarioBanana'], - (object) ['name' => 'gorilla'], - (object) ['name' => 'foo'], - ])->shouldBeCalledTimes(1); - - $this->initializerMock->getStore()->willReturn($this->storeMock->reveal())->shouldBeCalledTimes(1); - $this->readerMock->getMethodAnnotations($this->functionMock->reveal())->willReturn([ - $this->annotationMock->reveal(), - $this->annotationMock->reveal(), - ])->shouldBeCalledTimes(1); - $this->annotationMock->getArgument() - ->willReturn('scenarioBanana', 'scenarioBanana', 'gorilla', 'gorilla') - ->shouldBeCalled(); - $this->annotationMock->getName() - ->willReturn('scenarioBanana', 'scenarioBanana', 'scenarioBanana', 'scenarioGorilla', 'scenarioGorilla', 'scenarioGorilla') - ->shouldBeCalled(); - $this->storeMock->hasStateFragment('scenarioBanana')->willReturn(true)->shouldBeCalledTimes(1); - $this->storeMock->hasStateFragment('scenarioGorilla')->willReturn(true)->shouldBeCalledTimes(1); - $this->storeMock->hasStateFragment('foo')->shouldNotBeCalled(); - $this->storeMock->getStateFragment('scenarioBanana')->willReturn('Yummy banana!')->shouldBeCalledTimes(2); - $this->storeMock->getStateFragment('scenarioGorilla')->willReturn('Bonobo')->shouldBeCalledTimes(2); - $this->storeMock->getStateFragment('foo')->shouldNotBeCalled(); - - $this->organiserMock->organiseArguments($this->functionMock->reveal(), [ - 0 => 'scenarioBanana', - 1 => 'gorilla', - 'scenarioBanana' => 'Yummy banana!', - 2 => 'Yummy banana!', - 'gorilla' => 'Bonobo', - 3 => 'Bonobo', - ])->shouldBeCalledTimes(1); - - $this->organiser->organiseArguments($this->functionMock->reveal(), ['scenarioBanana', 'gorilla']); - } -} diff --git a/tests/Resolver/ArgumentsResolverTest.php b/tests/Resolver/ArgumentsResolverTest.php deleted file mode 100644 index 3badd3f..0000000 --- a/tests/Resolver/ArgumentsResolverTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Gorghoa\ScenarioStateBehatExtension\Resolver; - -use Doctrine\Common\Annotations\Reader; -use Gorghoa\ScenarioStateBehatExtension\Annotation\ScenarioStateArgument; -use Gorghoa\ScenarioStateBehatExtension\Context\Initializer\ScenarioStateInitializer; -use Gorghoa\ScenarioStateBehatExtension\ScenarioStateInterface; - -/** - * @author Vincent Chalamon - */ -class ArgumentsResolverTest extends \PHPUnit_Framework_TestCase -{ - public function testResolve() - { - $initializerMock = $this->prophesize(ScenarioStateInitializer::class); - $storeMock = $this->prophesize(ScenarioStateInterface::class); - $readerMock = $this->prophesize(Reader::class); - $functionMock = $this->prophesize(\ReflectionMethod::class); - $parameterMock = $this->prophesize(\ReflectionParameter::class); - $annotationMock = $this->prophesize(ScenarioStateArgument::class); - - $functionMock->getParameters()->willReturn([$parameterMock, $parameterMock])->shouldBeCalledTimes(2); - $parameterMock->getName()->willReturn('lorem', 'foo', 'lorem', 'foo')->shouldBeCalledTimes(4); - $initializerMock->getStore()->willReturn($storeMock)->shouldBeCalledTimes(1); - $readerMock->getMethodAnnotation($functionMock, ScenarioStateArgument::class)->willReturn($annotationMock)->shouldBeCalledTimes(1); - $readerMock->getMethodAnnotations($functionMock)->willReturn([$this->prophesize(\stdClass::class), $annotationMock])->shouldBeCalledTimes(1); - $annotationMock->getArgument()->willReturn('lorem')->shouldBeCalledTimes(2); - $annotationMock->getName()->willReturn('ipsum')->shouldBeCalledTimes(2); - $storeMock->hasStateFragment('ipsum')->willReturn(true)->shouldBeCalledTimes(1); - $storeMock->getStateFragment('ipsum')->willReturn('pouet')->shouldBeCalledTimes(1); - - $resolver = new ArgumentsResolver($initializerMock->reveal(), $readerMock->reveal()); - $resolver->resolve($functionMock->reveal(), ['lorem' => 'pouet', 'foo' => 'bar']); - } -} diff --git a/testsSAI/Argument/ArgumentOrganiserTest.php b/testsSAI/Argument/ArgumentOrganiserTest.php new file mode 100644 index 0000000..fc76a7f --- /dev/null +++ b/testsSAI/Argument/ArgumentOrganiserTest.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gorghoa\StepArgumentInjectorBehatExtension\Argument; + +use Doctrine\Common\Annotations\Reader; +use Gorghoa\StepArgumentInjectorBehatExtension\Annotation\StepInjectorArgument; +use Prophecy\Prophecy\ObjectProphecy; +use Behat\Testwork\Argument\ArgumentOrganiser as BehatArgumentOrganiser; + +/** + * @author Vincent Chalamon + * @author Rodrigue Villetard + */ +class ArgumentOrganiserTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ArgumentOrganiser + */ + private $organiser; + + /** + * @var ObjectProphecy|ArgumentOrganiser + */ + private $organiserMock; + + /** + * @var ObjectProphecy + */ + private $initializerMock; + + /** + * @var ObjectProphecy|\ReflectionMethod + */ + private $functionMock; + + /** + * @var ObjectProphecy|Reader + */ + private $readerMock; + + /** + * @var StepArgumentHolder + */ + private $holderMock; + + protected function setUp() + { + $this->organiserMock = $this->prophesize(BehatArgumentOrganiser::class); + $this->functionMock = $this->prophesize(\ReflectionMethod::class); + $this->readerMock = $this->prophesize(Reader::class); + $this->holderMock = $this->prophesize(StepArgumentHolder::class); + + $this->organiser = new ArgumentOrganiser( + $this->organiserMock->reveal(), + [$this->holderMock->reveal()], + $this->readerMock->reveal() + ); + } + + /** + * @return ObjectProphecy + */ + private function annotationMockFactory() + { + return $this->prophesize(StepInjectorArgument::class); + } + + public function testOrganiseArguments() + { + $this->functionMock->getParameters()->willReturn([ + (object) ['name' => 'scenarioBanana'], // argument with injector annotation and **a service hold** value + (object) ['name' => 'gorilla'], // argument with injector annotation but **no service hold** value + (object) ['name' => 'foo'], // argument not handled by this extension + ])->shouldBeCalledTimes(1); + + $annot1 = $this->annotationMockFactory(); + $annot1->getArgument()->willReturn('scenarioBanana')->shouldBeCalledTimes(1); + $annot1->reveal(); + + $annot2 = $this->annotationMockFactory(); + $annot2->getArgument()->willReturn('gorilla')->shouldBeCalledTimes(1); + $annot2->reveal(); + + $this->readerMock->getMethodAnnotations($this->functionMock->reveal())->willReturn([ + $annot1, + $annot2, + ])->shouldBeCalledTimes(1); + + $this->holderMock->doesHandleStepArgument($annot1)->willReturn(true); + $this->holderMock->doesHandleStepArgument($annot2)->willReturn(false); + + $this->holderMock->getStepArgumentValueFor($annot1)->willReturn('yammyBanana')->shouldBeCalledTimes(1); + $this->holderMock->getStepArgumentValueFor($annot2)->shouldNotBeCalled(); + + $this->holderMock->getStepArgumentValueFor($annot2); + + $this->organiserMock->organiseArguments($this->functionMock->reveal(), [ + 0 => 'scenarioBanana', + 1 => 'gorilla', + 'scenarioBanana' => 'yammyBanana', + 2 => 'yammyBanana', + ])->shouldBeCalledTimes(1); + + $this->organiser->organiseArguments($this->functionMock->reveal(), ['scenarioBanana', 'gorilla']); + } +} diff --git a/testsSAI/Resolver/ArgumentsResolverTest.php b/testsSAI/Resolver/ArgumentsResolverTest.php new file mode 100644 index 0000000..82e1443 --- /dev/null +++ b/testsSAI/Resolver/ArgumentsResolverTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gorghoa\StepArgumentInjectorBehatExtension\Resolver; + +use Doctrine\Common\Annotations\Reader; +use Gorghoa\StepArgumentInjectorBehatExtension\Annotation\StepInjectorArgument; +use Gorghoa\StepArgumentInjectorBehatExtension\Argument\StepArgumentHolder; + +/** + * @author Vincent Chalamon + * @author Rodrigue Villetard + */ +class ArgumentsResolverTest extends \PHPUnit_Framework_TestCase +{ + public function testResolve() + { + $readerMock = $this->prophesize(Reader::class); + $functionMock = $this->prophesize(\ReflectionMethod::class); + $parameterMock = $this->prophesize(\ReflectionParameter::class); + $annotationMock = $this->prophesize(StepInjectorArgument::class); + + $functionMock->getParameters()->willReturn([$parameterMock, $parameterMock])->shouldBeCalledTimes(2); + $parameterMock->getName()->willReturn('lorem', 'foo', 'lorem', 'foo')->shouldBeCalledTimes(4); + + $holderMock1 = $this->prophesize(StepArgumentHolder::class); + $holderMock2 = $this->prophesize(StepArgumentHolder::class); + + $readerMock->getMethodAnnotation($functionMock, StepInjectorArgument::class)->willReturn($annotationMock)->shouldBeCalledTimes(1); + $readerMock->getMethodAnnotations($functionMock)->willReturn([$this->prophesize(\stdClass::class), $annotationMock])->shouldBeCalledTimes(1); + $annotationMock->getArgument()->willReturn('lorem')->shouldBeCalledTimes(1); + + $holderMock1->doesHandleStepArgument($annotationMock)->willReturn(true)->shouldBeCalledTimes(1); + $holderMock1->getStepArgumentValueFor($annotationMock)->willReturn(true)->shouldBeCalledTimes(1); + + $holderMock2->doesHandleStepArgument($annotationMock)->willReturn(false)->shouldBeCalledTimes(1); + $holderMock2->getStepArgumentValueFor($annotationMock)->shouldNotBeCalled(); + + $resolver = new ArgumentsResolver([$holderMock1->reveal(), $holderMock2->reveal()], $readerMock->reveal()); + $resolver->resolve($functionMock->reveal(), ['lorem' => 'pouet', 'foo' => 'bar']); + } +}