State
Sharing state between scenariosβ
Don't do it.
Scenarios must be independent of one 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:
- 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 state in variables inside your step definitions.
State can make your steps more tightly coupled and harder to reuse.
- Java
- Kotlin
- Scala
- JavaScript
- Ruby
Cucumber will create a new instance of each of your glue code classes before each scenario so your steps will not leak state.
When using Spring there is an annotation 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.
Similarly, when using Guice there is a scope ScenarioScoped
that should always be used on step definition classes.
Cucumber will create a new instance of each of your glue code classes before each scenario so your steps will not leak state.
When using Spring there is an annotation 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.
Similarly, when using Guice there is a scope ScenarioScoped
that should always be used on step definition classes.
Cucumber will create a new instance of each of your glue code classes before each scenario so your steps will not leak state.
When using Spring there is an annotation 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.
Similarly, when using Guice there is a scope ScenarioScoped
that should always be used on step definition classes.
Cucumber-JS uses a World
as an isolated context for each scenario. You can find more information in the
docs on GitHub.
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.
Dependency injectionβ
This section is only applicable to JVM-based implementations of Cucumber.
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:
- PicoContainer (the recommended one if your application doesn't use another DI module)
- Spring
- Guice
- OpenEJB
- Weld
- Needle
PicoContainerβ
To use PicoContainer, add the following dependency to your pom.xml
:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>7.20.1</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-picocontainer', version: '7.20.1'
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>7.20.1</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-spring', version: '7.20.1'
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>7.20.1</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-guice', version: '7.20.1'
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>7.20.1</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-openejb', version: '7.20.1'
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>7.20.1</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-weld', version: '7.20.1'
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>7.20.1</version>
<scope>test</scope>
</dependency>
Or, if you are using Gradle, add:
compile group: 'io.cucumber', name: 'cucumber-needle', version: '7.20.1'
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.
Using 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 Cucumber object factory from 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 Cucumber object factory a 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 the Cucumber object factory with a test runner (JUnit 5/JUnit 4/TestNG)β
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 event busβ
This section is only applicable to JVM-based implementations of Cucumber.
Cucumber emits events on an event bus in many cases, e.g.:
- during the feature file parsing
- when the test scenarios are executed
Configuring the UUID generatorβ
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 |
| ~1 | Reports may be generated on different JVMs at the same time. A typical example would be one suite that tests against Firefox and another against Safari. The exact browser is configured through a property. These are then executed concurrently on different Gitlab runners. |
io.cucumber.core.eventbus.IncrementingUuidGenerator |
| ~130 | For developers wanting high performance and accepting potential UUID collisions when running in different classloaders / JVMs setups. |
The performance gain on real project depend on the feature size.
When the generator is not specified, the RandomUuidGenerator
is used.
Defining your own UUID 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:
- Creates a UUID class generator, e.g.:
package mypackage.mysubpackage;
import io.cucumber.core.eventbus.UuidGenerator;
public class MyUuidGenerator implements UuidGenerator {
@Override
public UUID generateId() {
return ...
}
}
- Define the UUID generator as SPI using a file
META-INF/services/io.cucumber.code.eventbus.UuidGenerator
on the classpath (e.g. Mavenresources
directory) containing the generator class name:
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.