1. Introduction

Cucumber hooks can come in handy when we want to perform specific actions for every scenario or step, but without having these actions explicitly in the Gherkin code.

In this tutorial, we'll look at the @Before@BeforeStep, @AfterStep, and @After Cucumber hooks.

2. Overview of Hooks in Cucumber

2.1. When Should Hooks Be Used?

Hooks can be used to perform background tasks that are not part of business functionality. Such tasks could be:

  • Starting up a browser
  • Setting or clearing cookies
  • Connecting to a database
  • Checking the state of the system
  • Monitoring

A use case for monitoring would be to update a dashboard with the test progress in real-time.

Hooks are not visible in the Gherkin code. Therefore, we should not see them as a replacement for a Cucumber Background or a given step.

We'll look at an example where we use hooks to take screenshots during test execution.

2.2. Scope of Hooks

Hooks affect every scenario. Therefore, it's good practice to define all hooks in a dedicated configuration class.

It's not necessary to define the same hooks in every glue code class. If we define hooks in the same class with our glue code, we'd have less readable code.

3. Hooks

Let's first look at the individual hooks. We'll then look at a full example where we'll see how hooks execute when combined.

3.1. @Before

Methods annotated with @Before will execute before every scenario. In our example, we'll start up the browser before every scenario:

@Before
public void initialization() {
    startBrowser();
}

If we annotate several methods with @Before, we can explicitly define the order in which the steps are executed:

@Before(order=2)
public void beforeScenario() {
    takeScreenshot();
}

The above method executes second, as we pass 2 as a value for the order parameter to the annotation. We can also pass 1 as a value for the order parameter of our initialization method:

@Before(order=1)
public void initialization()

So, when we execute a scenario, initialization() executes first, and beforeScenario() executes second.

3.2. @BeforeStep

Methods annotated with @BeforeStep execute before every step. Let's use the annotation to take a screenshot before every step:

@BeforeStep
public void beforeStep() {
    takeScreenshot();
}

3.3. @AfterStep

Methods annotated with @AfterStep execute after every step:

@AfterStep
public void afterStep() {
    takeScreenshot();
}

We've used @AfterStep here to take a screenshot after every step. This happens regardless of whether the step finishes successfully or fails.

3.4. @After

Methods annotated with @After execute after every scenario:

@After
public void afterScenario() {
    takeScreenshot();
    closeBrowser();
}

In our example, we'll take a final screenshot and close the browser. This happens regardless of whether the scenario finishes successfully.

3.5. The Scenario Parameter

The methods annotated with a hook annotation can accept a parameter of type Scenario:

@After
public void beforeScenario(Scenario scenario) { 
    // some code
}

The object of type Scenario contains information on the current scenario. Included are the scenario name, number of steps, names of steps, and status (pass or fail). This can be useful if we want to perform different actions for passed and failed tests.

4. Hook Execution

4.1. Happy Flow

Let's now look at what happens when we run a Cucumber scenario with all four types of hooks:

Feature: Book Store With Hooks
  Background: The Book Store
    Given The following books are available in the store
      | The Devil in the White City          | Erik Larson |
      | The Lion, the Witch and the Wardrobe | C.S. Lewis  |
      | In the Garden of Beasts              | Erik Larson |

  Scenario: 1 - Find books by author
    When I ask for a book by the author Erik Larson
    Then The salesperson says that there are 2 books

  Scenario: 2 - Find books by author, but isn't there
    When I ask for a book by the author Marcel Proust
    Then The salesperson says that there are 0 books

Looking at the result of a test run in the IntelliJ IDE, we can see the execution order:

First, our two @Before hooks execute. Then before and after every step, the @BeforeStep and @AfterStep hooks run, respectively. Finally, the @After hook runs. All hooks execute for both scenarios.

4.2. Unhappy Flow: a Step Fails

Let's see what happens if a step fails. As we can see in the screenshot below, both the @Before and @After hooks of the failing step are executed. The subsequent steps are skipped, and finally, the @After hook executes:

The behavior of @After is similar to the finally-clause after a try-catch in Java. We could use it to perform clean-up tasks if a step failed. In our example, we still take a screenshot even if the scenario fails.

4.3. Unhappy Flow: a Hook Fails

Let's look at what happens when a hook itself fails. In the example below, the first @BeforeStep fails.

In this case, the actual step doesn't run, but it's @AfterStep hook does. Subsequent steps won't run either, whereas the @After hook is executed at the end:

5. Conditional Execution with Tags

Hooks are defined globally and affect all scenarios and steps. However, with the help of Cucumber tags, we can define exactly which scenarios a hook should be executed for:

@Before(order=2, value="@Screenshots")
public void beforeScenario() {
    takeScreenshot();
}

This hook will be executed only for scenarios that are tagged with @Screenshots:

@Screenshots
Scenario: 1 - Find books by author 
When I ask for a book by the author Erik Larson 
Then The salesperson says that there are 2 books

6. Java 8

We can add Cucumber Java 8 Support to define all hooks with lambda expressions.

Recall our initialization hook from the example above:

@Before(order=2)
public void initialization() {
    startBrowser();
}

Rewritten with a lambda expression, we get:

public BookStoreWithHooksRunSteps() {
    Before(2, () -> startBrowser());
}

The same also works for @BeforeStep, @After, and @AfterStep.

7. Conclusion

In this article, we looked at how to define Cucumber hooks.

We discussed in which cases we should use them and when we should not. Then, we saw in which order hooks execute and how we can achieve conditional execution.

Finally, we saw how we could define hooks with Java 8 lambda notation.

As usual, the complete source code of this article is available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!