Hooking into the Test Process

You’ve learnedhow to write step definitions andthat withGherkin you can move common steps into abackground block, making your features DRY. But what if that’s not enough? Whatif you want to execute some code before the whole test suite or after aspecific scenario? Hooks to the rescue:

// features/bootstrap/FeatureContext.phpuseBehat\Behat\Context\Context;useBehat\Testwork\Hook\Scope\BeforeSuiteScope;useBehat\Behat\Hook\Scope\AfterScenarioScope;useBehat\Hook\AfterScenario;useBehat\Hook\BeforeSuite;classFeatureContextimplementsContext{#[BeforeSuite]publicstaticfunctionprepare(BeforeSuiteScope$scope){// prepare system for test suite// before it runs}#[AfterScenario('@database')]publicfunctioncleanDB(AfterScenarioScope$scope){// clean database after scenarios,// tagged with @database}}

Behat Hook System

Behat provides a number of hook points which allow us to run arbitrarylogic at various points in the Behat test cycle. Hooks are a lot likestep definitions or transformations - they are just simple methodswith special attributes inside your context classes. There is noassociation between where the hook is defined and which node it is runfor, but you can use tagged or named hooks if you want more fine grainedcontrol.

All defined hooks are run whenever the relevant action occurs. The actiontree looks something like this:

├── Suite #1│   ├── Feature #1│   │   ├── Scenario #1│   │   │   ├── Step #1│   │   │   └── Step #2│   │   └── Scenario #2│   │       ├── Step #1│   │       └── Step #2│   └── Feature #2│       └── Scenario #1│           └── Step #1└── Suite #2    └── Feature #1        └── Scenario #1            └── Step #1

This is a basic test cycle in Behat. There are many test suites, each ofwhich has many features, which themselves have many scenarios with manysteps. Note that when Behat actually runs, scenario outline examples areinterpreted as scenarios - meaning each outline example becomes an actualscenario in this action tree.

Hooks

Hooks allow you to execute your custom code just before or just after eachof these actions. Behat allows you to use the following hooks:

  1. TheBeforeSuite hook is run before any feature in the suite runs. Forexample, you could use this to set up the project you are testing. Thishook receives an optional argument with an instance of theBehat\Testwork\Hook\Scope\BeforeSuiteScope class.

  2. TheAfterSuite hook is run after all features in the suite have run.This hooks is useful to dump or print some kind of statistics or teardown your application after testing. This hook receives an optionalargument with an instance of theBehat\Testwork\Hook\Scope\AfterSuiteScope class.

  3. TheBeforeFeature hook is run before a feature runs. This hook receivesan optional argument with an instance of theBehat\Behat\Hook\Scope\BeforeFeatureScope class.

  4. TheAfterFeature hook is run after Behat finishes executing a feature.This hook receives an optional argument with an instance of theBehat\Behat\Hook\Scope\AfterFeatureScope class.

  5. TheBeforeScenario hook is run before a specific scenario will run. Thishook receives an optional argument with an instance of theBehat\Behat\Hook\Scope\BeforeScenarioScope class.

  6. TheAfterScenario hook is run after Behat finishes executing a scenario.This hook receives an optional argument with an instance of theBehat\Behat\Hook\Scope\AfterScenarioScope class.

  7. TheBeforeStep hook is run before a step runs. This hook receives anoptional argument with an instance of theBehat\Behat\Hook\Scope\BeforeStepScope class.

  8. TheAfterStep hook is run after Behat finishes executing a step. Thishook receives an optional argument with an instance of theBehat\Behat\Hook\Scope\AfterStepScope class.

You can use any of these hooks by adding attributes to any of your methods in your contextclass:

#[BeforeSuite]publicstaticfunctionprepare($scope){// prepare system for test suite// before it runs}

We use attributes as we did before withdefinitions.Simply use the attribute of the name of the hook you want to use (e.g.BeforeSuite).

Suite Hooks

Suite hooks are run outside of the scenario context. It means that your contextclass (e.g.FeatureContext) is not instantiated yet and the only way Behatcan execute code in it is through the static calls. That is why suite hooks mustbe defined as static methods in the context class:

useBehat\Testwork\Hook\Scope\BeforeSuiteScope;useBehat\Testwork\Hook\Scope\AfterSuiteScope;useBehat\Hook\AfterSuite;useBehat\Hook\BeforeSuite;#[BeforeSuite]publicstaticfunctionsetup(BeforeSuiteScope$scope){}#[AfterSuite]publicstaticfunctionteardown(AfterSuiteScope$scope){}

There are two suite hook types available:

  • BeforeSuite - executed before any feature runs.

  • AfterSuite - executed after all features have run.

Feature Hooks

Same as suite hooks, feature hooks are ran outside of the scenario context.So same as suite hooks, your feature hooks must be defined as static methodsinside your context:

useBehat\Behat\Hook\Scope\BeforeFeatureScope;useBehat\Behat\Hook\Scope\AfterFeatureScope;useBehat\Hook\AfterFeature;useBehat\Hook\BeforeFeature;#[BeforeFeature]publicstaticfunctionsetupFeature(BeforeFeatureScope$scope){}#[AfterFeature]publicstaticfunctionteardownFeature(AfterFeatureScope$scope){}

There are two feature hook types available:

  • BeforeFeature - gets executed before every feature in suite.

  • AfterFeature - gets executed after every feature in suite.

Scenario Hooks

Scenario hooks are triggered before or after each scenario runs. Thesehooks are executed inside an initialized context instance, so not only could theybe simple context instance methods, they will also have access toany object properties you set during your scenario:

useBehat\Behat\Hook\Scope\BeforeScenarioScope;useBehat\Behat\Hook\Scope\AfterScenarioScope;useBehat\Hook\AfterScenario;useBehat\Hook\BeforeScenario;#[BeforeScenario]publicfunctionbefore(BeforeScenarioScope$scope){}#[AfterScenario]publicfunctionafter(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.

Now, the interesting part:

TheBeforeScenario hook executes not onlybefore each scenario in each feature, but beforeeach example row inthe scenario outline. Yes, each scenario outline example row works almost thesame as a usual scenario.

AfterScenario functions exactly the same way, being executed both afterusual scenarios and outline examples.

Step Hooks

Step hooks are triggered before or after each step runs. These hooks arerun inside an initialized context instance, so they are just plain contextinstance methods in the same way as scenario hooks are:

useBehat\Behat\Hook\Scope\BeforeStepScope;useBehat\Behat\Hook\Scope\AfterStepScope;useBehat\Hook\AfterStep;useBehat\Hook\BeforeStep;#[BeforeStep]publicfunctionbeforeStep(BeforeStepScope$scope){}#[AfterStep]publicfunctionafterStep(AfterStepScope$scope){}

There are two step hook types available:

  • 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 aBeforeFeature,AfterFeature,BeforeScenario orAfterScenario hook with oneor more tags. You can also useOR (||) andAND (&&) tags:

#[BeforeScenario('@database,@orm')]publicfunctioncleanDatabase(){// clean database before// @database OR @orm scenarios}

Use the&& tag to execute a hook only when it hasall provided tags:

#[BeforeScenario('@database&&@fixtures')]publicfunctioncleanDatabaseFixtures(){// clean database fixtures// before @database @fixtures// scenarios}