Be careful with state
State can make your steps more tightly coupled and harder to reuse.
Don’t do it.
Scenarios must be independent from each other so it is important that state is not shared between scenarios. Accidentally leaking state from one scenario into others makes your scenarios brittle and also difficult to run in isolation.
To prevent accidentally leaking state between scenarios:
Before
hook.Before
hook.Within your scenarios, you might want to share state between steps.
It’s possible to store state in variables inside your step definitions.
Cucumber will create a new instance of each of your glue code classes before each scenario so your steps will not leak state.
ScenarioScope
that may be used on any spring beans that have been employed to share step state; this annotation ensures that these
beans do not persist across scenarios and so do not leak state.ScenarioScoped
that should always be used on step definition classes.See also Dependency Injection.
Cucumber will create a new instance of each of your glue code classes before each scenario so your steps will not leak state.
ScenarioScope
that may be used on any spring beans that have been employed to share step state; this annotation ensures that these
beans do not persist across scenarios and so do not leak state.ScenarioScoped
that should always be used on step definition classes.See also Dependency Injection.
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 uses a World
as an isolated context for each scenario. You can find more
information in the
cucumber-js documentation on GitHub.
If your programming language is a JVM language, you will be writing glue code (step definitions and hooks) in 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:
If your programming language is a JVM language, you will be writing glue code (step definitions and hooks) in 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:
To use PicoContainer, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-picocontainer', version: '7.17.0'
There is no documentation yet, but the code is on GitHub. For more information, please see sharing state using PicoContainer.
To use PicoContainer, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-picocontainer', version: '7.17.0'
There is no documentation yet, but the code is on GitHub. For more information, please see sharing state using PicoContainer.
To use Spring, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-spring', version: '7.17.0'
There is no documentation yet, but the code is on GitHub.
To use Spring, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-spring', version: '7.17.0'
There is no documentation yet, but the code is on GitHub.
To use Guice, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-guice</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-guice', version: '7.17.0'
There is no documentation yet, but the code is on GitHub. For more information, please see sharing state using Guice.
To use Guice, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-guice</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-guice', version: '7.17.0'
There is no documentation yet, but the code is on GitHub. For more information, please see sharing state using Guice.
To use OpenEJB, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-openejb</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-openejb', version: '7.17.0'
There is no documentation yet, but the code is on GitHub.
To use OpenEJB, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-openejb</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-openejb', version: '7.17.0'
There is no documentation yet, but the code is on GitHub.
To use Weld, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-weld</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-weld', version: '7.17.0'
There is no documentation yet, but the code is on GitHub.
To use Weld, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-weld</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-weld', version: '7.17.0'
There is no documentation yet, but the code is on GitHub.
To use Needle, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-needle</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-needle', version: '7.17.0'
There is no documentation yet, but the code is on GitHub.
To use Needle, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-needle</artifactId>
<version>7.17.0</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-needle', version: '7.17.0'
There is no documentation yet, but the code is on GitHub.
When using a DI framework all your step definitions, hooks, transformers, etc. will be created by the frameworks instance injector.
When using a DI framework all your step definitions, hooks, transformers, etc. will be created by the frameworks instance 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.
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.
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.
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.
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
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
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
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
The Cucumber modules for JUnit 4 and TestNG allow to run Cucumber through a JUnit/TestNG test.
The custom object factory can be configured using the @CucumberOptions
annotation. For JUnit 5 see the cucumber-junit-platform-engine documentation.
The Cucumber modules for JUnit 4 and TestNG allow to run Cucumber through a JUnit/TestNG test.
The custom object factory can be configured using the @CucumberOptions
annotation. For JUnit 5 see the cucumber-junit-platform-engine documentation.
Cucumber emits events on an event bus in many cases, e.g.: - during the feature file parsing - when the test scenarios are executed
Cucumber emits events on an event bus in many cases, e.g.: - during the feature file parsing - when the test scenarios are executed
Each event has a UUID as unique identifier. The UUID generator can be configured using the cucumber.uuid-generator
property:
| UUID generator | Features | Performance [Millions UUID/second] | Typical usage example |
|——————————————————-|————————————————————————————————————————————————————————————————————|————————————|——————————————————————————————————————————————————————————————————————————————————————————–|
| io.cucumber.core.eventbus.RandomUuidGenerator
|
io.cucumber.core.eventbus.IncrementingUuidGenerator
| The performance gain on real project depend on the feature size.
When the generator is not specified, the RandomUuidGenerator
is used.
Each event has a UUID as unique identifier. The UUID generator can be configured using the cucumber.uuid-generator
property:
| UUID generator | Features | Performance [Millions UUID/second] | Typical usage example |
|——————————————————-|————————————————————————————————————————————————————————————————————|————————————|——————————————————————————————————————————————————————————————————————————————————————————–|
| io.cucumber.core.eventbus.RandomUuidGenerator
|
io.cucumber.core.eventbus.IncrementingUuidGenerator
| The performance gain on real project depend on the feature size.
When the generator is not specified, the RandomUuidGenerator
is used.
When neither the RandomUuidGenerator
nor the IncrementingUuidGenerator
suits the project needs, a custom UUID generator can be defined. This is done using the following method:
java
package mypackage.mysubpackage;
import io.cucumber.core.eventbus.UuidGenerator;
public class MyUuidGenerator implements UuidGenerator {
@Override
public UUID generateId() {
return ...
}
}
META-INF/services/io.cucumber.code.eventbus.UuidGenerator
on the classpath (e.g. Maven resources
directory) containing the generator class name:
java
mypackage.mysubpackage.MyUuidGenerator
This UUID generator will override the default RandomUuidGenerator
.
When the META-INF/services/...
file contains more than one UUID generator or when there are multiple META-INF/services/...
on the classpath, the cucumber.uuid-generator
property must be configured to select the desired generator.
When neither the RandomUuidGenerator
nor the IncrementingUuidGenerator
suits the project needs, a custom UUID generator can be defined. This is done using the following method:
java
package mypackage.mysubpackage;
import io.cucumber.core.eventbus.UuidGenerator;
public class MyUuidGenerator implements UuidGenerator {
@Override
public UUID generateId() {
return ...
}
}
META-INF/services/io.cucumber.code.eventbus.UuidGenerator
on the classpath (e.g. Maven resources
directory) containing the generator class name:
java
mypackage.mysubpackage.MyUuidGenerator
This UUID generator will override the default RandomUuidGenerator
.
When the META-INF/services/...
file contains more than one UUID generator or when there are multiple META-INF/services/...
on the classpath, the cucumber.uuid-generator
property must be configured to select the desired generator.
There are several options to remove state from your database, to prevent leaking state between scenarios.
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.
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 Before
hook, and later
roll it back in an After
hook.
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
See the spring-java-junit5 example in Cucumber-JVM for a minimal setup.
See the spring-java-junit5 example in Cucumber-JVM for a minimal setup.
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 test data is explicitly deleted before each Scenario.
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
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.