diff --git a/cookbooks/accessing_contexts_from_each_other.rst b/cookbooks/accessing_contexts_from_each_other.rst index e712b33..b7082e7 100644 --- a/cookbooks/accessing_contexts_from_each_other.rst +++ b/cookbooks/accessing_contexts_from_each_other.rst @@ -13,13 +13,14 @@ so other contexts can be retrieved using a ``BeforeScenario`` hook: use Behat\Behat\Context\Context; use Behat\Behat\Hook\Scope\BeforeScenarioScope; + use Behat\Hook\BeforeScenario; class FeatureContext implements Context { /** @var \Behat\MinkExtension\Context\MinkContext */ private $minkContext; - /** @BeforeScenario */ + #[BeforeScenario] public function gatherContexts(BeforeScenarioScope $scope) { $environment = $scope->getEnvironment(); diff --git a/cookbooks/creating_a_context_configuration_extension.rst b/cookbooks/creating_a_context_configuration_extension.rst index 5f28fda..d8423d0 100644 --- a/cookbooks/creating_a_context_configuration_extension.rst +++ b/cookbooks/creating_a_context_configuration_extension.rst @@ -30,6 +30,7 @@ The configuration will also control whether the behaviour is enabled or not. use Behat\Behat\Context\Context as BehatContext; use Behat\Behat\Tester\Exception\PendingException; + use Behat\Step\Given; class HelloWorldContext implements BehatContext { @@ -45,7 +46,7 @@ The configuration will also control whether the behaviour is enabled or not. $this->text = $text; } - /** @Given I say Hello World */ + #[Given('I say Hello World')] public function helloWorld() { if ($this->enable) { diff --git a/quick_start.rst b/quick_start.rst index 2ade898..2cb8090 100644 --- a/quick_start.rst +++ b/quick_start.rst @@ -270,13 +270,11 @@ Finally, we got to the automation part. How does Behat know what to do when it sees ``Given there is a "Sith Lord Lightsaber", which costs £5``? You tell it. You write PHP code inside your context class (``FeatureContext`` in our case) and tell Behat that this code represents a specific scenario step -(via an annotation with a pattern): +(via an attribute with a pattern): .. code-block:: php - /** - * @Given there is a(n) :arg1, which costs £:arg2 - */ + #[Given('there is a(n) :arg1, which costs £:arg2')] public function thereIsAWhichCostsPs($arg1, $arg2) { throw new PendingException(); @@ -284,16 +282,19 @@ in our case) and tell Behat that this code represents a specific scenario step .. note:: - ``/** ... */`` is a special syntax in PHP called a doc-block. It is - discoverable at runtime and used by different PHP frameworks as a - way to provide additional meta-information for the classes, methods and - functions. Behat uses doc-blocks for step definitions, step - transformations and hooks. + Behat uses PHP Attributes for step definitions, step + transformations and hooks. It also supports doc-block + annotations for compatibility with legacy code, but this + syntax is deprecated - see the :doc:`annotations ` documentation + for details. -``@Given there is a(n) :arg1, which costs £:arg2`` above the method tells Behat +``#[Given('there is a(n) :arg1, which costs £:arg2')]`` above the method tells Behat that this particular method should be executed whenever Behat sees step that -looks like ``... there is a ..., which costs £...``. This pattern will match -any of the following steps: +looks like ``... there is a ..., which costs £...``. + +The ``#[Given]``, ``#[When]`` and ``#[Then]`` attributes are functionally identical - they +only exist separately to help keep the wording of your step definitions readable. So +this pattern will match any of the following steps: .. code-block:: gherkin @@ -324,9 +325,7 @@ Not only that, but Behat will capture tokens (words starting with ``:``, e.g. .. code-block:: php - /** - * @Given /there is an? \"([^\"]+)\", which costs £([\d\.]+)/ - */ + #[Given('/there is an? \"([^\"]+)\", which costs £([\d\.]+)/')] public function thereIsAWhichCostsPs($arg1, $arg2) { throw new PendingException(); @@ -341,9 +340,7 @@ got: --- FeatureContext has missing steps. Define them with these snippets: - /** - * @Given there is a :arg1, which costs £:arg2 - */ + #[Given('there is a :arg1, which costs £:arg2')] public function thereIsAWhichCostsPs($arg1, $arg2) { throw new PendingException(); @@ -370,36 +367,31 @@ If you executed ``--append-snippets``, your ``FeatureContext`` should look like: use Behat\Behat\Context\SnippetAcceptingContext; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; + use Behat\Step\Given; + use Behat\Step\Then; + use Behat\Step\When; class FeatureContext implements SnippetAcceptingContext { - /** - * @Given there is a :arg1, which costs £:arg2 - */ + #[Given('there is a :arg1, which costs £:arg2')] public function thereIsAWhichCostsPs($arg1, $arg2) { throw new PendingException(); } - /** - * @When I add the :arg1 to the basket - */ + #[When('I add the :arg1 to the basket')] public function iAddTheToTheBasket($arg1) { throw new PendingException(); } - /** - * @Then I should have :arg1 product(s) in the basket - */ + #[Then('I should have :arg1 product(s) in the basket')] public function iShouldHaveProductInTheBasket($arg1) { throw new PendingException(); } - /** - * @Then the overall basket price should be £:arg1 - */ + #[Then('the overall basket price should be £:arg1')] public function theOverallBasketPriceShouldBePs($arg1) { throw new PendingException(); @@ -433,6 +425,9 @@ code we could come up with to fulfil our scenario. Something like this: use Behat\Behat\Context\SnippetAcceptingContext; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; + use Behat\Step\Given; + use Behat\Step\Then; + use Behat\Step\When; class FeatureContext implements SnippetAcceptingContext { @@ -445,25 +440,19 @@ code we could come up with to fulfil our scenario. Something like this: $this->basket = new Basket($this->shelf); } - /** - * @Given there is a :product, which costs £:price - */ + #[Given('there is a :arg1, which costs £:arg2')] public function thereIsAWhichCostsPs($product, $price) { $this->shelf->setProductPrice($product, floatval($price)); } - /** - * @When I add the :product to the basket - */ + #[When('I add the :arg1 to the basket')] public function iAddTheToTheBasket($product) { $this->basket->addProduct($product); } - /** - * @Then I should have :count product(s) in the basket - */ + #[Then('I should have :arg1 product(s) in the basket')] public function iShouldHaveProductInTheBasket($count) { // Normally you would import this class - we are using the fully qualified name @@ -474,9 +463,7 @@ code we could come up with to fulfil our scenario. Something like this: ); } - /** - * @Then the overall basket price should be £:price - */ + #[Then('the overall basket price should be £:arg1')] public function theOverallBasketPriceShouldBePs($price) { \PHPUnit\Framework\Assert::assertSame( diff --git a/releases.rst b/releases.rst index 9eec51d..4f3949e 100644 --- a/releases.rst +++ b/releases.rst @@ -48,7 +48,8 @@ Behat only supports current versions of PHP and third-party dependency packages By "current", we mean: -* PHP versions that are listed as receiving active support or security fixes on the `official php.net version support page`_. +* PHP versions that are listed as receiving active support or security fixes + on the `official php.net version support page`_. * Symfony versions that are listed as maintained or receiving security fixes on the `official Symfony releases page`_. Once a PHP or Symfony version reaches End of Life we will remove it from our composer.json and CI flows. @@ -56,7 +57,8 @@ Once a PHP or Symfony version reaches End of Life we will remove it from our com .. note:: When we drop support for a PHP / dependency version we will highlight this in the CHANGELOG, but we will treat it as a minor release. Composer will automatically protect users from upgrading to a version that does not support - their environment. Users running Behat as a ``.phar`` should review the release notes before downloading a new version. + their environment. Users running Behat as a ``.phar`` should review the release notes before downloading + a new version. We will not ship bugfix releases for unsupported PHP / dependency versions, unless: diff --git a/user_guide.rst b/user_guide.rst index 5b88be9..c474d7d 100644 --- a/user_guide.rst +++ b/user_guide.rst @@ -10,6 +10,7 @@ User Guide user_guide/writing_scenarios user_guide/organizing user_guide/context + user_guide/annotations user_guide/command_line_tool user_guide/configuration user_guide/integrations diff --git a/user_guide/annotations.rst b/user_guide/annotations.rst new file mode 100644 index 0000000..2abc910 --- /dev/null +++ b/user_guide/annotations.rst @@ -0,0 +1,118 @@ +Annotations +=========== + +Historically Behat used doc-block annotations instead of attributes to define steps, hooks and +transformations in PHP contexts. These annotations are still available for now, and you can use them instead +of PHP attributes in your projects - however they will likely be deprecated and removed in the future. + +Step Annotations +---------------- + +Here is an example of how you can define your steps using annotations: + +.. code-block:: php + + // features/bootstrap/FeatureContext.php + + use Behat\Behat\Context\Context; + + class FeatureContext implements Context + { + /* + * @Given we have some context + */ + public function prepareContext() + { + // do something + } + + /* + * @When an :event occurs + */ + public function onEvent(string $event) + { + // do something + } + + /* + * @Then something should be done + */ + public function checkOutcomes() + { + // do something + } + } + +The pattern that you would include as an argument to the step attribute should be listed here +after the annotation, separated by a space + +Hook Annotations +---------------- + +Here is an example of how you can define your hooks using annotations: + +.. code-block:: php + + // features/bootstrap/FeatureContext.php + + use Behat\Behat\Context\Context; + use Behat\Testwork\Hook\Scope\BeforeSuiteScope; + use Behat\Behat\Hook\Scope\AfterScenarioScope; + + class FeatureContext implements Context + { + /* + * @BeforeSuite + */ + public static function prepare(BeforeSuiteScope $scope) + { + } + + /* + * @AfterScenario @database + */ + public function cleanDB(AfterScenarioScope $scope) + { + } + } + +Transformation Annotations +-------------------------- + +Here is an example of how you can define your transformations using annotations: + +.. code-block:: php + + // features/bootstrap/FeatureContext.php + + use Behat\Behat\Context\Context; + + class FeatureContext implements Context + { + /* + * @Transform /^(\d+)$/ + */ + public function castStringToNumber($string) + { + return intval($string); + } + } + +Existing code +------------- + +Even though annotations are still available, they will probably be deprecated and eventually removed in the future. +Therefore, we do not recommend using annotations for new projects. If your current project uses annotations, we +recommend that you refactor your code to use PHP attributes instead. This can be done manually but you should be able +to use the search and replace capabilities of your IDE to do this in a more automated way. + +Alternatively you may want to use a tool like `Rector`_ which can do automated refactoring of your code. Rector 2.0 +includes a rule that allows you to automatically convert any doc-block annotations to the corresponding attributes. +To use it for your Behat contexts, add the following option to your Rector configuration: + +.. code-block:: php + + ->withAttributesSets(behat: true) + +.. _`Rector`: https://github.com/rectorphp/rector + diff --git a/user_guide/context.rst b/user_guide/context.rst index 23517e2..9f58053 100644 --- a/user_guide/context.rst +++ b/user_guide/context.rst @@ -16,6 +16,10 @@ application behaves, then the context class is all about how to test it. // features/bootstrap/FeatureContext.php use Behat\Behat\Context\Context; + use Behat\Hook\BeforeFeature; + use Behat\Step\Given; + use Behat\Step\Then; + use Behat\Step\When; class FeatureContext implements Context { @@ -24,25 +28,25 @@ application behaves, then the context class is all about how to test it. // instantiate context } - /** @BeforeFeature */ + #[BeforeFeature] public static function prepareForTheFeature() { // clean database or do other preparation stuff } - /** @Given we have some context */ + #[Given('we have some context')] public function prepareContext() { // do something } - /** @When event occurs */ + #[When('event occurs')] public function doSomeAction() { // do something } - /** @Then something should be done */ + #[Then('something should be done')] public function checkOutcomes() { // do something @@ -117,8 +121,8 @@ has its very own context instance. This means 2 things: #. Every step in a single scenario is executed inside a common context instance. This means you can set ``private`` instance variables inside - your ``@Given`` steps and you'll be able to read their new values inside - your ``@When`` and ``@Then`` steps. + your ``Given`` steps and you'll be able to read their new values inside + your ``When`` and ``Then`` steps. Multiple Contexts ----------------- @@ -356,9 +360,7 @@ That means if you put some step definitions or hooks inside a trait: trait ProductsDictionary { - /** - * @Given there is a(n) :product, which costs £:price - */ + #[Given('there is a(n) :product, which costs £:price')] public function thereIsAWhichCostsPs($product, $price) { throw new PendingException(); diff --git a/user_guide/context/definitions.rst b/user_guide/context/definitions.rst index 97a2e12..b0303ab 100644 --- a/user_guide/context/definitions.rst +++ b/user_guide/context/definitions.rst @@ -10,9 +10,7 @@ PHP code called step definitions: .. code-block:: php - /** - * @When I do something with :argument - */ + #[When('I do something with :argument')] public function iDoSomethingWith($argument) { // do something with $argument @@ -37,33 +35,26 @@ patterns bound to each method in your ``FeatureContext``. If the line of Gherkin satisfies a bound pattern, its corresponding step definition is executed. It's that simple! -Behat uses php-doc annotations to bind patterns to ``FeatureContext`` methods: +Behat uses PHP attributes to bind patterns to ``FeatureContext`` methods: .. code-block:: php - /** - * @When I do something with :methodArgument - */ + #[When('I do something with :methodArgument')] public function someMethod($methodArgument) {} Let's take a closer look at this code: -#. ``@When`` is a definition keyword. There are 3 supported keywords in - annotations: ``@Given``/``@When``/``@Then``. These three definition keywords +#. ``When`` is a definition keyword. There are 3 supported keywords implemented as + attributes: ``Given``/``When``/``Then``. These three definition keywords are actually equivalent, but all three are available so that your step definition remains readable. -#. The text after the keyword is the step text pattern (e.g. +#. The argument to the attribute is the step text pattern (e.g. ``I do something with :methodArgument``). #. All token values of the pattern (e.g. ``:methodArgument``) will be captured and passed to the method argument with the same name (``$methodArgument``). -.. note:: - - Notice the comment block starts with ``/**``, and not the usual ``/*``. - This is important for Behat to be able to parse such comments as annotations! - As you have probably noticed, this pattern is quite general and its corresponding method will be called for steps that contain ``... I do something with ...``, including: @@ -104,26 +95,22 @@ a different argument: .. note:: Behat does not differentiate between step keywords when matching patterns - to methods. So a step defined with ``@When`` could also be matched to - ``@Given ...``, ``@Then ...``, ``@And ...``, ``@But ...``, etc. + to methods. So a step defined with ``When`` could also be matched to + ``Given ...``, ``Then ...``, ``And ...``, ``But ...``, etc. Your step definitions can also define multiple arguments to pass to its matching ``FeatureContext`` method: .. code-block:: php - /** - * @When I do something with :stringArgument and with :numberArgument - */ + #[When('I do something with :stringArgument and with :numberArgument')] public function someMethod($stringArgument, $numberArgument) {} You can also specify alternative words and optional parts of words, like this: .. code-block:: php - /** - * @When there is/are :count monster(s) - */ + #[When('there is/are :count monster(s)')] public function thereAreMonsters($count) {} If you need to come up with a much more complicated matching algorithm, you can @@ -131,21 +118,17 @@ always use good old regular expressions: .. code-block:: php - /** - * @When /^there (?:is|are) (\d+) monsters?$/i - */ + #[When('/^there (?:is|are) (\d+) monsters?$/i')] public function thereAreMonsters($count) {} And if you want to be able to say things in different ways that are not so -easily written as a single regular expression, you can write multiple -annotations for the one method: +easily written as a single regular expression, you can add multiple +attributes for the one method: .. code-block:: php - /** - * @When /^I create (\d+) monsters$/i - * @Given /^(\d+) monster(?:s|) (?:have|has) been created$/i - */ + #[When('/^I create (\d+) monsters$/i')] + #[Given('/^(\d+) monster(?:s|) (?:have|has) been created$/i')] public function thereAreMonsters($count) {} Behat will call the corresponding method if any of the patterns matches. @@ -163,7 +146,7 @@ Definition Snippets ------------------- You now know how to write step definitions by hand, but writing all these -method stubs, annotations and patterns by hand is tedious. Behat makes +method stubs, attributes and patterns by hand is tedious. Behat makes this routine task much easier and fun by generating definition snippets for you! Let's pretend that you have this feature: @@ -183,7 +166,7 @@ interface and you test a feature with missing steps in Behat: Behat will provide auto-generated snippets for your context class. -It not only generates the proper definition annotation type (``@Given``), but +It not only generates the proper definition attribute type (``Given``), but also a proper pattern with tokens capturing (``:arg1``, ``:arg2``), method name (``someStepWithArgument()``, ``numberStepWith()``) and arguments ( ``$arg1``, ``$arg2``), all based just on the text of the step. Isn't that cool? @@ -252,15 +235,16 @@ Let's pretend our context class contains the code below: // features/bootstrap/FeatureContext.php use Behat\Behat\Context\Context; + use Behat\Step\Given; class FeatureContext implements Context { - /** @Given some step with :argument1 argument */ + #[Given('some step with :argument1 argument')] public function someStepWithArgument($argument1) { } - /** @Given number step with :argument1 */ + #[Given('number step with :argument1')] public function numberStepWith($argument1) { } @@ -279,7 +263,7 @@ execution. Enable the "posix" PHP extension in order to see colorful Behat output. Depending on your Linux, Mac OS or other Unix system it might be part - of the default PHP installation or a separate ``php5-posix`` package. + of the default PHP installation or a separate ``posix`` package. Undefined Steps ~~~~~~~~~~~~~~~ @@ -333,16 +317,17 @@ Let's pretend your ``FeatureContext`` looks like this: use Behat\Behat\Context\Context; use Behat\Behat\Tester\Exception\PendingException; + use Behat\Step\Given; class FeatureContext implements Context { - /** @Given some step with :argument1 argument */ + #[Given('some step with :argument1 argument')] public function someStepWithArgument($argument1) { throw new PendingException('Do some string work'); } - /** @Given number step with :argument1 */ + #[Given('number step with :argument1')] public function numberStepWith($argument1) { throw new PendingException('Do some number work'); @@ -383,16 +368,17 @@ Let's pretend, that your ``FeatureContext`` has following code: // features/bootstrap/FeatureContext.php use Behat\Behat\Context\Context; + use Behat\Step\Given; class FeatureContext implements Context { - /** @Given some step with :argument1 argument */ + #[Given('some step with :argument1 argument')] public function someStepWithArgument($argument1) { throw new Exception('some exception'); } - /** @Given number step with :argument1 */ + #[Given(number step with :argument1')] public function numberStepWith($argument1) { } @@ -467,15 +453,16 @@ Consider your ``FeatureContext`` has following code: // features/bootstrap/FeatureContext.php use Behat\Behat\Context\Context; + use Behat\Step\Given; class FeatureContext implements Context { - /** @Given /^.* step with .*$/ */ + #[Given('/^.* step with .*$/')] public function someStepWithArgument() { } - /** @Given /^number step with (\d+)$/ */ + #[Given('/^number step with (\d+)$/')] public function numberStepWith($argument1) { } @@ -492,7 +479,7 @@ Redundant Step Definitions ~~~~~~~~~~~~~~~~~~~~~~~~~~ Behat will not let you define a step expression's corresponding pattern more -than once. For example, look at the two ``@Given`` patterns defined in this +than once. For example, look at the two ``Given`` patterns defined in this feature context: .. code-block:: php @@ -500,15 +487,16 @@ feature context: // features/bootstrap/FeatureContext.php use Behat\Behat\Context\Context; + use Behat\Step\Given; class FeatureContext implements Context { - /** @Given /^number step with (\d+)$/ */ + #[Given('/^number step with (\d+)$/')] public function workWithNumber($number1) { } - /** @Given /^number step with (\d+)$/ */ + #[Given('/^number step with (\d+)$/')] public function workDifferentlyWithNumber($number1) { } @@ -530,9 +518,9 @@ Each transformation method must return a new value. This value then replaces the original string value that was going to be used as an argument to a step definition method. -Transformation methods are defined using the same annotation style as step -definition methods, but instead use the ``@Transform`` keyword, followed by -a matching pattern. +Transformation methods are defined using the same attribute style as step +definition methods, but instead use the ``Transform`` attribute with +a matching pattern as argument. As a basic example, you can automatically cast all numeric arguments to integers with the following context class code: @@ -542,20 +530,18 @@ integers with the following context class code: // features/bootstrap/FeatureContext.php use Behat\Behat\Context\Context; + use Behat\Step\Then; + use Behat\Transformation\Transform; class FeatureContext implements Context { - /** - * @Transform /^(\d+)$/ - */ + #[Transform('/^(\d+)$/')] public function castStringToNumber($string) { return intval($string); } - /** - * @Then a user :name, should have :count followers - */ + #[Then('a user :name, should have :count followers')] public function assertUserHasFollowers($name, $count) { if ('integer' !== gettype($count)) { @@ -579,20 +565,18 @@ will create and return a new ``User`` object: // features/bootstrap/FeatureContext.php use Behat\Behat\Context\Context; + use Behat\Step\Then; + use Behat\Transformation\Transform; class FeatureContext implements Context { - /** - * @Transform :user - */ + #[Transform(':user')] public function castUsernameToUser($user) { return new User($user); } - /** - * @Then a :user, should have :count followers - */ + #[Then('a :user, should have :count followers')] public function assertUserHasFollowers(User $user, $count) { if ('integer' !== gettype($count)) { @@ -627,12 +611,11 @@ And our ``FeatureContext`` class looks like this: use Behat\Behat\Context\Context; use Behat\Gherkin\Node\TableNode; + use Behat\Step\Given; class FeatureContext implements Context { - /** - * @Given the following users: - */ + #[Given('the following users:')] public function pushUsers(TableNode $usersTable) { $users = array(); @@ -665,12 +648,13 @@ via a comma-delimited list of the column headers prefixed with ``table:``: use Behat\Behat\Context\Context; use Behat\Gherkin\Node\TableNode; + use Behat\Step\Given; + use Behat\Step\Then; + use Behat\Transformation\Transform; class FeatureContext implements Context { - /** - * @Transform table:name,followers - */ + #[Transform('table:name,followers')] public function castUsersTable(TableNode $usersTable) { $users = array(); @@ -684,17 +668,13 @@ via a comma-delimited list of the column headers prefixed with ``table:``: return $users; } - /** - * @Given the following users: - */ + #[Given('the following users:')] public function pushUsers(array $users) { // do something with $users } - /** - * @Then I expect the following users: - */ + #[Then('I expect the following users:')] public function assertUsers(array $users) { // do something with $users diff --git a/user_guide/context/hooks.rst b/user_guide/context/hooks.rst index 943b61f..066ff40 100644 --- a/user_guide/context/hooks.rst +++ b/user_guide/context/hooks.rst @@ -14,25 +14,23 @@ specific scenario? Hooks to the rescue: use Behat\Behat\Context\Context; use Behat\Testwork\Hook\Scope\BeforeSuiteScope; use Behat\Behat\Hook\Scope\AfterScenarioScope; + use Behat\Hook\AfterScenario; + use Behat\Hook\BeforeSuite; class FeatureContext implements Context { - /** - * @BeforeSuite - */ - public static function prepare(BeforeSuiteScope $scope) - { - // prepare system for test suite - // before it runs - } - - /** - * @AfterScenario @database - */ - public function cleanDB(AfterScenarioScope $scope) - { - // clean database after scenarios, - // tagged with @database + #[BeforeSuite] + public static function prepare(BeforeSuiteScope $scope) + { + // prepare system for test suite + // before it runs + } + + #[AfterScenario('@database')] + public function cleanDB(AfterScenarioScope $scope) + { + // clean database after scenarios, + // tagged with @database } } @@ -42,7 +40,7 @@ Behat Hook System Behat provides a number of hook points which allow us to run arbitrary logic at various points in the Behat test cycle. Hooks are a lot like step definitions or transformations - they are just simple methods -with special annotations inside your context classes. There is no +with special attributes inside your context classes. There is no association between where the hook is defined and which node it is run for, but you can use tagged or named hooks if you want more fine grained control. @@ -117,23 +115,21 @@ of these actions. Behat allows you to use the following hooks: hook receives an optional argument with an instance of the ``Behat\Behat\Hook\Scope\AfterStepScope`` class. -You can use any of these hooks by annotating any of your methods in your context +You can use any of these hooks by adding attributes to any of your methods in your context class: .. code-block:: php - /** - * @BeforeSuite - */ + #[BeforeSuite] public static function prepare($scope) { // prepare system for test suite // before it runs } -We use annotations as we did before with :doc:`definitions `. -Simply use the annotation of the name of the hook you want to use (e.g. -``@BeforeSuite``). +We use attributes as we did before with :doc:`definitions `. +Simply use the attribute of the name of the hook you want to use (e.g. +``BeforeSuite``). Suite Hooks ----------- @@ -147,21 +143,23 @@ be defined as static methods in the context class: use Behat\Testwork\Hook\Scope\BeforeSuiteScope; use Behat\Testwork\Hook\Scope\AfterSuiteScope; + use Behat\Hook\AfterSuite; + use Behat\Hook\BeforeSuite; - /** @BeforeSuite */ + #[BeforeSuite] public static function setup(BeforeSuiteScope $scope) { } - /** @AfterSuite */ + #[AfterSuite] public static function teardown(AfterSuiteScope $scope) { } There are two suite hook types available: -* ``@BeforeSuite`` - executed before any feature runs. -* ``@AfterSuite`` - executed after all features have run. +* ``BeforeSuite`` - executed before any feature runs. +* ``AfterSuite`` - executed after all features have run. Feature Hooks ------------- @@ -174,13 +172,15 @@ inside your context: use Behat\Behat\Hook\Scope\BeforeFeatureScope; use Behat\Behat\Hook\Scope\AfterFeatureScope; + use Behat\Hook\AfterFeature; + use Behat\Hook\BeforeFeature; - /** @BeforeFeature */ + #[BeforeFeature] public static function setupFeature(BeforeFeatureScope $scope) { } - /** @AfterFeature */ + #[AfterFeature] public static function teardownFeature(AfterFeatureScope $scope) { } @@ -188,8 +188,8 @@ inside your context: There are two feature hook types available: -* ``@BeforeFeature`` - gets executed before every feature in suite. -* ``@AfterFeature`` - gets executed after every feature in suite. +* ``BeforeFeature`` - gets executed before every feature in suite. +* ``AfterFeature`` - gets executed after every feature in suite. Scenario Hooks -------------- @@ -203,30 +203,32 @@ any object properties you set during your scenario: use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\Behat\Hook\Scope\AfterScenarioScope; + use Behat\Hook\AfterScenario; + use Behat\Hook\BeforeScenario; - /** @BeforeScenario */ + #[BeforeScenario] public function before(BeforeScenarioScope $scope) { } - /** @AfterScenario */ + #[AfterScenario] public function after(AfterScenarioScope $scope) { } There are two scenario hook types available: -* ``@BeforeScenario`` - executed before every scenario in each feature. -* ``@AfterScenario`` - executed after every scenario in each feature. +* ``BeforeScenario`` - executed before every scenario in each feature. +* ``AfterScenario`` - executed after every scenario in each feature. Now, the interesting part: -The ``@BeforeScenario`` hook executes not only +The ``BeforeScenario`` hook executes not only before each scenario in each feature, but before **each example row** in the scenario outline. Yes, each scenario outline example row works almost the same as a usual scenario. -``@AfterScenario`` functions exactly the same way, being executed both after +``AfterScenario`` functions exactly the same way, being executed both after usual scenarios and outline examples. Step Hooks @@ -240,13 +242,15 @@ instance methods in the same way as scenario hooks are: use Behat\Behat\Hook\Scope\BeforeStepScope; use Behat\Behat\Hook\Scope\AfterStepScope; + use Behat\Hook\AfterStep; + use Behat\Hook\BeforeStep; - /** @BeforeStep */ + #[BeforeStep] public function beforeStep(BeforeStepScope $scope) { } - /** @AfterStep */ + #[AfterStep] public function afterStep(AfterStepScope $scope) { } @@ -254,22 +258,20 @@ instance methods in the same way as scenario hooks are: There are two step hook types available: -* ``@BeforeStep`` - executed before every step in each scenario. -* ``@AfterStep`` - executed after every step in each scenario. +* ``BeforeStep`` - executed before every step in each scenario. +* ``AfterStep`` - executed after every step in each scenario. Tagged Hooks ------------ Sometimes you may want a certain hook to run only for certain scenarios, -features or steps. This can be achieved by associating a ``@BeforeFeature``, -``@AfterFeature``, ``@BeforeScenario`` or ``@AfterScenario`` hook with one +features or steps. This can be achieved by associating a ``BeforeFeature``, +``AfterFeature``, ``BeforeScenario`` or ``AfterScenario`` hook with one or more tags. You can also use ``OR`` (``||``) and ``AND`` (``&&``) tags: .. code-block:: php - /** - * @BeforeScenario @database,@orm - */ + #[BeforeScenario('@database,@orm')] public function cleanDatabase() { // clean database before @@ -280,9 +282,7 @@ Use the ``&&`` tag to execute a hook only when it has *all* provided tags: .. code-block:: php - /** - * @BeforeScenario @database&&@fixtures - */ + #[BeforeScenario('@database&&@fixtures')] public function cleanDatabaseFixtures() { // clean database fixtures diff --git a/user_guide/writing_scenarios.rst b/user_guide/writing_scenarios.rst index e5cb450..1db7175 100644 --- a/user_guide/writing_scenarios.rst +++ b/user_guide/writing_scenarios.rst @@ -322,12 +322,11 @@ usually as input to a ``Given`` or as expected output from a ``Then``: .. code-block:: php use Behat\Gherkin\Node\TableNode; + use Behat\Step\Given; // ... - /** - * @Given the following people exist: - */ + #[Given('the following people exist:')] public function thePeopleExist(TableNode $table) { foreach ($table as $row) { @@ -373,12 +372,11 @@ three double-quote marks (``"""``), placed on their own line: .. code-block:: php use Behat\Gherkin\Node\PyStringNode; + use Behat\Step\Given; // ... - /** - * @Given a blog post named :title with: - */ + #[Given('a blog post named :title with:')] public function blogPost($title, PyStringNode $markdown) { $this->createPost($title, $markdown->getRaw());