How to Share Data Between Steps in Cucumber
Cucumber is a popular BDD (Behavior-Driven Development) framework that helps bridge the communication gap between developers, testers, and business analysts. Often, while writing step definitions, we need to share data between steps. This is necessary when actions in one step affect subsequent steps in the scenario. Let us delve into understanding how Cucumber shares data between steps to maintain scenario continuity and improve test automation efficiency.
1. Setup
To use Cucumber with Spring, ensure you have the following dependencies in your pom.xml
file:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | < dependencies > < dependency > < groupId >io.cucumber</ groupId > < artifactId >cucumber-java</ artifactId > < version >latest__jar__version</ version > </ dependency > < dependency > < groupId >io.cucumber</ groupId > < artifactId >cucumber-spring</ artifactId > < version >latest__jar__version</ version > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > </ dependency > </ dependencies > |
Now, let’s configure Spring to work with Cucumber by creating a SpringConfiguration
class.
1 2 3 4 5 6 7 8 9 | package com.example.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan (basePackages = "com.example" ) public class SpringConfiguration { } |
2. Share Data Between Steps Using Spring
The recommended way to share data between steps in Cucumber with Spring is to use a ScenarioContext
class, which acts as a shared storage.
2.1 Create ScenarioContext Class
The ScenarioContext
class in Cucumber provides a way to share data between steps in a test scenario, ensuring continuity across different step definitions. It is implemented as a Spring-managed component using @Component
, making it injectable into step definition classes. The class maintains a HashMap
called dataMap
to store key-value pairs dynamically. The set(String key, Object value)
method allows storing any object, while the generic get(String key, Class<T> clazz)
method retrieves and type-casts the stored data. This approach improves test modularity by preventing reliance on static variables, making test cases cleaner and more maintainable. With ScenarioContext
, Cucumber steps can efficiently pass data, enabling seamless execution of complex test workflows.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | package com.example.context; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class ScenarioContext { private Map<String, Object> dataMap = new HashMap<>(); public void set(String key, Object value) { dataMap.put(key, value); } public <T> T get(String key, Class<T> clazz) { return clazz.cast(dataMap.get(key)); } } |
2.1.1 Code Explanation
The given Java code defines a Spring component named ScenarioContext
, which serves as a thread-safe container for storing and retrieving key-value pairs dynamically at runtime. The @Component
annotation ensures that Spring manages this class as a bean, allowing it to be injected where needed. Internally, it utilizes a HashMap<String, Object>
named dataMap
to store data generically. The set
method enables storing a value against a specified key, while the generic get
method retrieves the value by casting it to the desired type, ensuring type safety. This utility is particularly useful in scenarios such as automated testing frameworks or request-scoped contexts where maintaining temporary test data or session-related information is required.
2.2 Inject ScenarioContext in Step Definitions
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package com.example.steps; import com.example.context.ScenarioContext; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; import io.cucumber.java.en.Then; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import static org.junit.Assert.assertEquals; @Component public class MyStepDefinitions { @Autowired private ScenarioContext scenarioContext; @Given ( "I set the user name to {string}" ) public void iSetTheUserNameTo(String userName) { scenarioContext.set( "userName" , userName); } @When ( "I retrieve the user name" ) public void iRetrieveTheUserName() { String storedName = scenarioContext.get( "userName" , String. class ); scenarioContext.set( "retrievedUserName" , storedName); } @Then ( "the retrieved user name should be {string}" ) public void theRetrievedUserNameShouldBe(String expectedUserName) { String actualUserName = scenarioContext.get( "retrievedUserName" , String. class ); assertEquals(expectedUserName, actualUserName); } } |
2.2.1 Code Explanation
The given Java code defines a Spring component named MyStepDefinitions
, which is a step definition class for Cucumber-based BDD (Behavior-Driven Development) testing. The class is managed by Spring as a bean due to the @Component
annotation. It leverages dependency injection via the @Autowired
annotation to access an instance of ScenarioContext
, a shared storage utility for test data. The @Given
step stores a user name in ScenarioContext
, the @When
step retrieves it and stores it again under a different key, and the @Then
step verifies that the retrieved user name matches the expected value using assertEquals
. This approach ensures data persistence across steps within a test scenario, making it useful for maintaining state between different phases of a BDD test execution.
2.3 Create a Feature File
Define the feature file in src/test/resources/features/share_data.feature
:
1 2 3 4 5 | Feature: Share data between steps Scenario: Storing and retrieving a user name Given I set the user name to "Yatin" When I retrieve the user name Then the retrieved user name should be "Yatin" |
2.4 Create Cucumber Test Runner
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | package com.example.runner; import io.cucumber.junit.Cucumber; import io.cucumber.junit.CucumberOptions; import org.junit.runner.RunWith; @RunWith (Cucumber. class ) @CucumberOptions ( features = "src/test/resources/features" , glue = "com.example.steps" , plugin = { "pretty" , "html:target/cucumber-report.html" } ) public class CucumberTestRunner { } |
2.4.1 Code Explanation
The given Java code defines a test runner class named CucumberTestRunner
, which is responsible for executing Cucumber tests in a JUnit environment. The @RunWith(Cucumber.class)
annotation integrates Cucumber with JUnit, allowing test scenarios to be executed as JUnit tests. The @CucumberOptions
annotation configures the test execution by specifying the feature files’ location using features = "src/test/resources/features"
, the step definitions package with glue = "com.example.steps"
, and the output format using the plugin
option, which includes a readable console output (“pretty”) and an HTML report stored at target/cucumber-report.html
. This setup ensures structured and automated execution of BDD test scenarios while generating reports for analysis.
2.4.2 Code Output
When the test suite is executed, the Cucumber scenario will run step by step, storing the username “Yatin” in ScenarioContext
, retrieving it in the next step, and verifying it in the final assertion step. The console output would be:
1 2 3 4 5 | Feature: Share data between steps Scenario: Storing and retrieving a user name Given I set the user name to "Yatin" ✅ Passed When I retrieve the user name ✅ Passed Then the retrieved user name should be "Yatin" ✅ Passed |
The test successfully passes, demonstrating how data is shared across steps using a Spring-managed context. This approach ensures modular and reusable step definitions, making test automation more structured and maintainable.
3. Conclusion
Sharing data between steps in Cucumber can be efficiently handled using Spring’s dependency injection. The ScenarioContext
class acts as a shared data store, ensuring smooth data transfer between step definitions. This approach makes step definitions modular, reusable, and easier to manage in large test suites.