51

To pass variables between steps I have the step methods belong to the same class, and use fields of the class for the passed information.

Here is an example as follows:

Feature: Demo

  Scenario: Create user
    Given User creation form management
    When Create user with name "TEST"
    Then User is created successfully

Java class with steps definitions:

public class CreateUserSteps {

   private String userName;

   @Given("^User creation form management$")
   public void User_creation_form_management() throws Throwable {
      // ...
   }

   @When("^Create user with name \"([^\"]*)\"$")
   public void Create_user_with_name(String userName) throws Throwable {
      //...
      this.userName = userName;
   }

   @Then("^User is created successfully$")
   public void User_is_created_successfully() throws Throwable {
      // Assert if exists an user with name equals to this.userName
   }

My question is if it is a good practice to share information between steps? Or would be better to define the feature as:

Then User with name "TEST" is created successfully
Raedwald
  • 40,290
  • 35
  • 127
  • 207
troig
  • 6,257
  • 4
  • 35
  • 60
  • 3
    Your strategy works well with BDD frameworks where you can link a class of definitions with a specific feature file. Cucumber doesn't support this (well, requires more effort than you'd like: http://confessionsofanagilecoach.blogspot.com/2017/05/teaching-cucumbers-about-boundaries.html ). Better to use the World strategy mentioned below or instead use JBehave. – Lance Kind May 03 '17 at 21:25
  • 3
    I published an article with example. Refer https://link.medium.com/SUg0onMmWT – Arun Chandrasekaran Feb 01 '19 at 01:32

7 Answers7

40

In order to share commonalities between steps you need to use a World. In Java it is not as clear as in Ruby.

Quoting the creator of Cucumber.

The purpose of a "World" is twofold:

  1. Isolate state between scenarios.

  2. Share data between step definitions and hooks within a scenario.

How this is implemented is language specific. For example, in ruby, the implicit self variable inside a step definition points to the current scenario's World object. This is by default an instance of Object, but it can be anything you want if you use the World hook.

In Java, you have many (possibly connected) World objects.

The equivalent of the World in Cucumber-Java is all of the objects with hook or stepdef annotations. In other words, any class with methods annotated with @Before, @After, @Given and so on will be instantiated exactly once for each scenario.

This achieves the first goal. To achieve the second goal you have two approaches:

a) Use a single class for all of your step definitions and hooks

b) Use several classes divided by responsibility [1] and use dependency injection [2] to connect them to each other.

Option a) quickly breaks down because your step definition code becomes a mess. That's why people tend to use b).

[1] https://cucumber.io/docs/gherkin/step-organization/

[2] PicoContainer, Spring, Guice, Weld, OpenEJB, Needle

The available Dependency Injection modules are:

  • cucumber-picocontainer
  • cucumber-guice
  • cucumber-openejb
  • cucumber-spring
  • cucumber-weld
  • cucumber-needle

Original post here https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y.

Hope this helps.

Pedro Lopez
  • 2,176
  • 2
  • 16
  • 27
  • 1
    For Java, I use a `MyScenarioScopedData` custom object as "World" with `@RequestScoped` and `@Inject` it into the class that defines the Cucumber steps. Each time a new scenario is created, a new `MyScenarioScopedData` is created. – Julien Kronegg Mar 13 '17 at 14:55
  • Those wiki links are now dangling links. – Raedwald Aug 14 '20 at 13:25
10

It's fine to share data between steps defined within a class using an instance variable. If you need to share data between steps in different classes you should look at the DI integrations (PicoContainer is the simplest).

In the example you show, I'd ask whether showing "TEST" in the scenario is necessary at all. The fact that the user is called TEST is an incidental detail and makes the scenario less readable. Why not generate a random name (or hard code something) in Create_user_with_name()?

Seb Rose
  • 3,500
  • 15
  • 29
  • 1
    Thanks for your very helpfull answer! I'll deep into PicoContainer. – troig Oct 18 '14 at 14:35
  • How can dependency injection share state between classes? – fijiaaron Nov 03 '16 at 22:14
  • 1
    @fijiaaron If you use any DI tool such as pico(Constructor injection in case of pico) then the responsibility of creating step defs and hook classes are taken over by DI. New instance of these classes are created for each scenario. Plus any other class instance that are required by this step defs are created by the DI for each scenario as defined in the step def constructor. Then the same instance of each class is passed to all the step defs that need it. Thus the state is passed around different step definitions. – Grasshopper Nov 05 '16 at 20:35
  • 1
    @fijiaaron have a look at this: http://stackoverflow.com/questions/34449948/how-to-pass-variable-values-between-steps-in-cucumber-java/34531404#34531404 Does that help at all? – Seb Rose Nov 06 '16 at 03:46
  • Hi @Seb Rose. After being more than a year working with cucumber-jvm, now I believe Pedo Lopez's answer covers more use cases. Anyway, I also think pico-container is probably the best DI for cucumber, but not the only one. That's the reason for why I've decided changing the accepted answer. Thanks again for you anwer and hope you understand. – troig Nov 23 '16 at 10:42
5

In Pure java, I just use a Singleton object that gets created once and cleared after tests.

public class TestData_Singleton {
    private static TestData_Singleton myself = new TestData_Singleton();

    private TestData_Singleton(){ }

    public static TestData_Singleton getInstance(){
        if(myself == null){
            myself = new TestData_Singleton();
        }

        return myself;
    }

    public void ClearTestData(){
        myself = new TestData_Singleton();
    }
Jason Smiley
  • 275
  • 4
  • 12
  • The benefit of using Cucumber's DI integrations is that you don't have to remember to `ClearTestData` before each step - Cucumber handles that for you. – Seb Rose Nov 06 '16 at 03:43
  • This approach worked perfect for me. Without having to change a lot of code. +1 for pure java – Chai Mar 09 '17 at 11:06
  • @jjason-smiley just curious how you achieved `use a Singleton object that gets created once and cleared after tests`. The approach that I'm using now is to have a `World` class with singleton and then a `WorldInitializer` with a single method annotated with the `@Before` (so before each scenario) that initializes/resets the singleton instance – Javier Mr Apr 15 '21 at 18:42
4

I would say that there are reasons to share information between steps, but I don't think that's the case in this scenario. If you propagate the user name via the test steps then it's not really clear from the feature what's going on. I think it's better to specifically say in the scenario what is expected. I would probably do something like this:

Feature: Demo

  Scenario: Create user
    Given User creation form management
    When Create user with name "TEST"
    Then A user named "TEST" has been created

Then, your actual test steps might look something like:

@When("^Create user with name \"([^\"]*)\"$")
public void Create_user_with_name(String userName) throws Throwable {
   userService.createUser(userName);
}

@Then("^A user named \"([^\"]*)\" has been created$")
public void User_is_created_successfully(String userName) throws Throwable {
   assertNotNull(userService.getUser(userName));
}
BarrySW19
  • 3,421
  • 9
  • 23
  • Thanks for your input @BarrySW19. I know that the example of my question is not too good.. But, really, I wanted to know if to share info between steps is a good practise in general. I'll keep in mind your suggestion – troig Oct 23 '14 at 12:38
3

Here my way: I define a custom Scenario-Scope with spring every new scenario there will be a fresh context

Feature      @Dummy
  Scenario: zweites Scenario
   When Eins
   Then Zwei

1: Use spring

<properties>
<cucumber.version>1.2.5</cucumber.version>
<junit.version>4.12</junit.version>
</properties>

<!-- cucumber section -->


<dependency>
  <groupId>info.cukes</groupId>
  <artifactId>cucumber-java</artifactId>
  <version>${cucumber.version}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>info.cukes</groupId>
  <artifactId>cucumber-junit</artifactId>
  <version>${cucumber.version}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>${junit.version}</version>
  <scope>test</scope>
</dependency>

 <dependency> 
   <groupId>info.cukes</groupId> 
   <artifactId>cucumber-spring</artifactId> 
   <version>${cucumber.version}</version> 
   <scope>test</scope> 
 </dependency> 


<!-- end cucumber section -->

<!-- spring-stuff -->
<dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-test</artifactId> 
              <version>4.3.4.RELEASE</version> 
       <scope>test</scope> 
 </dependency> 

   <dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-context</artifactId> 
              <version>4.3.4.RELEASE</version> 
       <scope>test</scope>
   </dependency> 
   <dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-tx</artifactId> 
       <version>4.3.4.RELEASE</version> 
       <scope>test</scope>
   </dependency> 
   <dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-core</artifactId> 
       <version>4.3.4.RELEASE</version> 
       <scope>test</scope>
       <exclusions> 
           <exclusion> 
               <groupId>commons-logging</groupId> 
               <artifactId>commons-logging</artifactId> 
           </exclusion> 
       </exclusions> 
   </dependency> 
   <dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-beans</artifactId> 
              <version>4.3.4.RELEASE</version> 
       <scope>test</scope>
   </dependency> 

   <dependency> 
       <groupId>org.springframework.ws</groupId> 
       <artifactId>spring-ws-core</artifactId> 
       <version>2.4.0.RELEASE</version> 
       <scope>test</scope>
   </dependency> 

2: build custom scope class

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(scopeName="scenario")
public class ScenarioContext {

    public Scenario getScenario() {
        return scenario;
    }

    public void setScenario(Scenario scenario) {
        this.scenario = scenario;
    }

    public String shareMe;
}

3: usage in stepdef

@ContextConfiguration(classes = { CucumberConfiguration.class })
public class StepdefsAuskunft {

private static Logger logger = Logger.getLogger(StepdefsAuskunft.class.getName());

@Autowired
private ApplicationContext applicationContext;

// Inject service here : The impl-class need @Primary @Service
// @Autowired
// IAuskunftservice auskunftservice;


public ScenarioContext getScenarioContext() {
    return (ScenarioContext) applicationContext.getBean(ScenarioContext.class);
}


@Before
public void before(Scenario scenario) {

    ConfigurableListableBeanFactory beanFactory = ((GenericApplicationContext) applicationContext).getBeanFactory();
    beanFactory.registerScope("scenario", new ScenarioScope());

    ScenarioContext context = applicationContext.getBean(ScenarioContext.class);
    context.setScenario(scenario);

    logger.fine("Context für Scenario " + scenario.getName() + " erzeugt");

}

@After
public void after(Scenario scenario) {

    ScenarioContext context = applicationContext.getBean(ScenarioContext.class);
    logger.fine("Context für Scenario " + scenario.getName() + " gelöscht");

}



@When("^Eins$")
public void eins() throws Throwable {
    System.out.println(getScenarioContext().getScenario().getName());
    getScenarioContext().shareMe = "demo"
    // you can save servicecall here
}

@Then("^Zwei$")
public void zwei() throws Throwable {
    System.out.println(getScenarioContext().getScenario().getName());
    System.out.println(getScenarioContext().shareMe);
    // you can use last service call here
}


@Configuration
    @ComponentScan(basePackages = "i.am.the.greatest.company.cucumber")
    public class CucumberConfiguration {
    }

the scope class

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;


public class ScenarioScope implements Scope {


  private Map<String, Object> objectMap = Collections.synchronizedMap(new HashMap<String, Object>());

    /** (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#get(java.lang.String, org.springframework.beans.factory.ObjectFactory)
     */
    public Object get(String name, ObjectFactory<?> objectFactory) {
        if (!objectMap.containsKey(name)) {
            objectMap.put(name, objectFactory.getObject());
        }
        return objectMap.get(name);

    }

    /** (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#remove(java.lang.String)
     */
    public Object remove(String name) {
        return objectMap.remove(name);
    }

    /** (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String, java.lang.Runnable)
     */
    public void registerDestructionCallback(String name, Runnable callback) {
        // do nothing
    }

    /** (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#resolveContextualObject(java.lang.String)
     */
    public Object resolveContextualObject(String key) {
        return null;
    }

    /** (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#getConversationId()
     */
    public String getConversationId() {
        return "VolatileScope";
    }

    /**
     * vaporize the beans
     */
    public void vaporize() {
        objectMap.clear();
    }


}
user528322
  • 61
  • 2
  • why do you need custom Context, isn't Prototype will be enough? – nahab Jan 26 '17 at 13:55
  • 2
    Anytime Spring enters the equation complexity really jumps. (Not poster's fault as he followed Spring's recipe.) I mean we've just tripled? the lines of code and it's all structure code. Not 'getting work done' code. And this isn't all off it. Off camera you've got a SpringCofiguration and Scenario. This is a good example (though missing those two pieces). But who wants to go through all that drudgery to make Cucumber get shared state? – Lance Kind May 03 '17 at 21:35
1

If you are using Serenity framework with cucumber you can use current session.

Serenity.getCurrentSession()

more about this feature in http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/. (Serenity was called Thucydides before)

bsmk
  • 1,082
  • 1
  • 13
  • 24
1

Other option is to use ThreadLocal storage. Create a context map and add them to the map. Cucumber JVM runs all the steps in the same thread and you have access to that across all the steps. To make it easier, you can instantiate the storage in before hook and clear in after hook.

samfromco
  • 19
  • 1