State

It’s important to prevent state created by one scenario from leaking into others. Leaking state makes your scenarios brittle, and difficult to run in isolation.

To prevent leaking state between scenarios:

  • Avoid using global or static variables.
  • Make sure you clean your database in a Before hook.
  • If you share a browser between scenarios, delete cookies in a Before hook.

Sharing state between steps πŸ”—︎

Within your scenarios, you might want to share state between steps.

It’s possible to store object state in variables inside your step definitions.

Be careful with state

State can make your steps more tightly coupled and harder to reuse.

World object πŸ”—︎

In Ruby, Cucumber runs scenarios in a World. By default, the World is an instance of Object.

All step definitions will run in the context of the current World instance; a new instance is created for each scenario. This means that self in a step definition block will be the World instance. Any @instance_variable instantiated in a step definition will be assigned to the World, and can be accessed from other step definitions.

If you want to add any behaviour to the world, like helper methods, or logging, you can do this in support/env.rb:

module CustomWorld
  def a_helper
    ...
  end
end

World(CustomWorld)

Now you can call a_helper from your step definitions.

Note that every scenario is run in a separate instance of the world, so there is no implicit state-sharing from scenario to scenario.

You can also include modules in your World:

module MyHelper
  def some_other_helper
    ...
  end
end

module CustomWorld
  include MyHelper

  def a_helper
    ...
  end
end

World(CustomWorld)

Several other frameworks such as Rspec or Webrat have modules that provide special methods that you can include in your World this way.

If you don’t want to define your own World class (and just use the default Object instances), you can still include modules in your World instances without polluting Object with a global include:

module MyHelper
  def some_other_helper
    ...
  end
end

module MyOtherHelpers
  def helper_b
    ...
  end
end

World(MyHelper, MyOtherHelpers)

This will extend each new World object with those modules.

If you use Ruby on Rails, there is already a World set up for you, so you will get an instance of Cucumber::Rails::World, which is a subclass of ActionDispatch::IntegrationTest. This gives you access to a lot of Rails’ helper methods.

Cucumber-js also uses a World as an isolated context for each scenario. You can find more information in the cucumber-js documentation on GitHub.

Dependency Injection πŸ”—︎

If your programming language is Java, you will be writing glue code (step definitions and hooks) in plain old Java classes.

Cucumber will create a new instance of each of your glue code classes before each scenario.

If all of your glue code classes have an empty constructor, you don’t need anything else. However, most projects will benefit from a dependency injection (DI) module to organize your code better and to share state between step definitions.

The available dependency injection modules are:

PicoContainer πŸ”—︎

To use PicoContainer, add the following dependency to your pom.xml:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-picocontainer</artifactId>
    <version>4.7.4</version>
    <scope>test</scope>
</dependency>

Or, if you are using Gradle, add:

compile group: 'io.cucumber', name: 'cucumber-picocontainer', version: '4.7.4'

There is no documentation yet, but the code is on GitHub. For more information, please see sharing state using Picocontainer.

Spring πŸ”—︎

To use Spring, add the following dependency to your pom.xml:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-spring</artifactId>
    <version>4.7.4</version>
    <scope>test</scope>
</dependency>

Or, if you are using Gradle, add:

compile group: 'io.cucumber', name: 'cucumber-spring', version: '4.7.4'

There is no documentation yet, but the code is on GitHub.

Guice πŸ”—︎

To use Guice, add the following dependency to your pom.xml:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-guice</artifactId>
    <version>4.7.4</version>
    <scope>test</scope>
</dependency>

Or, if you are using Gradle, add:

compile group: 'io.cucumber', name: 'cucumber-guice', version: '4.7.4'

There is no documentation yet, but the code is on GitHub. For more information, please see sharing state using Guice.

OpenEJB πŸ”—︎

To use OpenEJB, add the following dependency to your pom.xml:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-openejb</artifactId>
    <version>4.7.4</version>
    <scope>test</scope>
</dependency>

Or, if you are using Gradle, add:

compile group: 'io.cucumber', name: 'cucumber-openejb', version: '4.7.4'

There is no documentation yet, but the code is on GitHub.

Weld πŸ”—︎

To use Weld, add the following dependency to your pom.xml:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-weld</artifactId>
    <version>4.7.4</version>
    <scope>test</scope>
</dependency>

Or, if you are using Gradle, add:

compile group: 'io.cucumber', name: 'cucumber-weld', version: '4.7.4'

There is no documentation yet, but the code is on GitHub.

Needle πŸ”—︎

To use Needle, add the following dependency to your pom.xml:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-needle</artifactId>
    <version>4.7.4</version>
    <scope>test</scope>
</dependency>

Or, if you are using Gradle, add:

compile group: 'io.cucumber', name: 'cucumber-needle', version: '4.7.4'

There is no documentation yet, but the code is on GitHub.

How to use DI πŸ”—︎

When using a DI framework all your step definitions, hooks, transformers, etc. will be created by the frameworks instance injector.

The need for a custom injector πŸ”—︎

Cucumber example tests are typically small and have no dependencies. In real life, though, tests often need access to application specific object instances which also need to be supplied by the injector. These instances need to be made available to your step definitions so that actions can be applied on them and delivered results can be tested.

The reason using Cucumber with a DI framework typically originates from the fact that the tested application also uses the same framework. So we need to configure a custom injector to be used with Cucumber. This injector ties tests and application instances together.

Here is an example of a typical step definition using Google Guice. Using the Cucumber provided Guice injector will fail to instantiate the required appService member.

package com.example.app;

import static org.junit.Assert.assertTrue;

import io.cucumber.java.en.When;
import io.cucumber.java.en.Then;
import io.cucumber.guice.ScenarioScoped;
import com.example.app.service.AppService;
import java.util.Objects;
import javax.inject.Inject;

@ScenarioScoped
public final class StepDefinition {

    private final AppService appService;

    @Inject
    public StepDefinition( AppService appService ) {
        this.appService = Objects.requireNonNull( appService, "appService must not be null" );
    }

    @When("the application services are started")
    public void startServices() {
        this.appService.startServices();
    }

    @Then("all application services should be running")
    public void checkThatApplicationServicesAreRunning() {
        assertTrue( this.appService.servicesAreRunning() );
    }
}

The implementation of the AppService may need further arguments and configuration that typically has to be provided by a Guice module. Guice modules are used to configure an injector and might look like this:

package com.example.app.service.impl;

import com.example.app.service.AppService;
import com.google.inject.AbstractModule;

public final class ServiceModule extends AbstractModule {
    @Override
    protected void configure() {
        bind( AppService.class ).to( AppServiceImpl.class );
        // ... (further bindings)
    }
}

The actual injector is then created like this: injector = Guice.createInjector( new ServiceModule() );

This means we need to create our own injector and tell Cucumber to use it.

The Cucumber object factory πŸ”—︎

Whenever Cucumber needs a specific object, it uses an object factory. Cucumber has a default object factory that (in case of Guice) creates a default injector and delegates object creation to that injector. If you want to customize the injector we need to provide our own object factory and tell Cucumber to use it instead.

package com.example.app;

import io.cucumber.core.backend.ObjectFactory;
import io.cucumber.guice.CucumberModules;
import io.cucumber.guice.ScenarioScope;
import com.example.app.service.impl.ServiceModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;

public final class CustomObjectFactory implements ObjectFactory {

    private Injector injector;

    public CustomObjectFactory() {
        // Create an injector with our service module
        this.injector =
          Guice.createInjector( Stage.PRODUCTION, CucumberModules.createScenarioModule(), new ServiceModule());
    }

    @Override
    public boolean addClass( Class< ? > clazz ) {
        return true;
    }

    @Override
    public void start() {
        this.injector.getInstance( ScenarioScope.class ).enterScope();
    }

    @Override
    public void stop() {
        this.injector.getInstance( ScenarioScope.class ).exitScope();
    }

    @Override
    public < T > T getInstance( Class< T > clazz ) {
        return this.injector.getInstance( clazz );
    }
}

This is the default object factory for Guice except that we have added our own bindings to the injector. Cucumber loads the object factory through the java.util.ServiceLoader. In order for the ServiceLoader to be able to pick up our custom implementation we need to provide the file META-INF/services/io.cucumber.core.backend.ObjectFactory.

com.example.app.CustomObjectFactory
#
# ... additional custom object factories could be added here
#

Now we have to tell Cucumber to use our custom object factory. There are several ways how this could be accomplished.

Using the command line πŸ”—︎

When Cucumber is run from the command line, the custom object factory can be specified as argument.

java io.cucumber.core.cli.Main --object-factory com.example.app.CustomObjectFactory

Using the property file πŸ”—︎

Cucumber makes use of a properties file (cucumber.properties) if it exists. The custom object factory can be specified in this file and will be picked up when Cucumber is running. The following entry needs to be available in the cucumber.properties file:

cucumber.object-factory=com.example.app.CustomObjectFactory

Using a test runner (JUnit/TestNG) πŸ”—︎

The Cucumber modules for JUnit and TestNG allow to run Cucumber through a JUnit/TestNG test. The custom object factory can be configured using the @CucumberOptions annotation.

Databases πŸ”—︎

There are several options to remove state from your database, to prevent leaking state between scenarios.

The Before Hook Approach πŸ”—︎

The recommended approach to clean a database between scenarios is to use a Before hook to remove all data before a scenario starts.

This is usually better than using an After hook, as it allows you to perform a post-mortem inspection of the database if a scenario fails.

An alternative approach is to use database transactions.

The Database Transaction Approach πŸ”—︎

If your database supports it, you can wrap a transaction around each scenario.

This might lead to faster scenarios, but it comes at a cost. You won’t be able to perform a post-mortem, and you won’t be able to use browser automation.

To use this approach, you need to tell Cucumber to start a transaction in a Beforehook, and later roll it back in an Afterhook.

This is such a common thing to do that several Cucumber extensions provide ready-to-use conditional hooks using a tag named @txn.

To enable it, you must tag every feature or scenario that requires transactions with @txn:

@txn
Feature: Let's write a lot of stuff to the DB

  Scenario: I clean up after myself
    Given I write to the DB

  Scenario: And so do I!
    Given I write to the DB

With JUnit and Spring πŸ”—︎

See the spring-txn example in Cucumber-JVM for a minimal setup.

Browser Automation and Transactions πŸ”—︎

If you’re using a browser automation tool that talks to your application over HTTP, the transactional approach will not work if your step definitions and the web application serving HTTP request each have their own database connection. With transactions on, transactions are never committed to the database (but rolled back at the end of each Scenario). Therefore, the web server’s connection will never see data from Cucumber, and therefore your browser won’t either. Likewise, Cucumber’s connection won’t see data from the web server.

In this case, you will have to turn off database transactions and make sure the data is explicitly deleted before each Scenario.

Turn off transactions πŸ”—︎

If you’re using Ruby on Rails, you can turn off transactions for a feature or particular scenarios. Use the @no-txn tag, like this:

@no-txn
Feature: Lots of Scenarios with transactions off.

If this is the case you should use the brute-force approach where the data is explicitly deleted before each scenario. Or this:

Feature: ...
  @no-txn
  Scenario: One Scenario with transactions off.

With Rails, you can also turn off transaction globally in your features/support/env.rb:

Cucumber::Rails::World.use_transactional_fixtures = false

Cleaning Your Database πŸ”—︎

If you’re using Ruby on Rails, a good tool to deal with this is Ben Mabey’s Database Cleaner gem, which you can install with gem install database_cleaner.

You can use this very effectively with the @no-txn tag. For example, add something like the following somewhere in e.g. features/support/db_cleaner.rb:

require 'database_cleaner'
DatabaseCleaner.clean_with :truncation # clean once to ensure clean slate
DatabaseCleaner.strategy = :truncation

Before('@no-txn') do
  DatabaseCleaner.start
end

After('@no-txn') do
  DatabaseCleaner.clean
end

If you’re not using Rails, you can recreate the entire @no-txn behaviour using DatabaseCleaner in Ruby with the following code:

# With this you should be able to tag the stories that need to use truncation.
# Otherwise, the transaction strategy will be used all the other times.

require 'database_cleaner'
DatabaseCleaner.clean_with :truncation # clean once to ensure clean slate
DatabaseCleaner.strategy = :transaction # use transactions by default

Before('@no-txn') do
  DatabaseCleaner.strategy = :truncation
end

Before do
  DatabaseCleaner.start
end

After do
  DatabaseCleaner.clean
end

After('@no-txn') do
  DatabaseCleaner.strategy = :transaction
end

You can help us improve this documentation. Edit this page.