Sharing state between steps in Cucumber-JVM using PicoContainer

A scenario in Gherkin is created by steps. Each step depends on previous steps. This means that we must be able to share state between steps.

The glue between Gherkin and the system under test are implemented as regular Java methods and implemented in regular Java classes. The steps are global in the sense that every step in the same package or subpackage relative to the runner will be found and executed. This allows us to define one step in one class and another step in another class.

Dividing steps between many classes may be a good idea. It is, however, probably not needed early in a project. The first reasonable division should therefore probably be no division. When you write your first scenario, you will most likely only have a few steps. Not more than that they can fit into one class without too much hassle. The first class with steps is probably small and you can easily find your way around in it.

The problem with too large step classes doesn't occur until after a while. You have added a bunch of scenarios to your project and finding your way around in the step definition class is getting harder and harder. The problem with large classes are that they

are not coherent, they are about a lot of different things at the same time

don't follow the single responsibility principle, there are probably many reasons why a class has to be changed

are messy, it is hard to find what you need

You would like to split the steps into multiple classes. The question is how.

Splitting according to concept

The good thing with global steps is that they allow us to divide steps along different axes. The decision on how to split is the same as when you decide which functionality goes in which class. This is hard, but something good developers do all the time. The more they learn about the problem and the domain, the more natural the division will be.

One way to split the steps may be according to the domain concept they work on.

Divide steps between different classes according to something that is logical for the team. All steps regarding goods goes into one step class and all steps regarding customers in another. Other axes are possible and perhaps better. If you make a mistake and realize it, move the methods to a better home. Moving the steps around is not a humongous task.

The next problem you will have to solve is to handle a shared state between the steps. When there was only one class, an instance variable or two was probably enough. Now you need to solve the problem with a shared state between the two, or more, classes with steps.

Dependency injection

How do you share state between different classes?

In Cucumber for Ruby, there is a world object where the shared state lives. It is re-created for each scenario. Each scenario has a fresh world and leakage between scenarios through the world object is unlikely.

A naive solution in Java could be to share a state using a class with static fields. This would work. It is unfortunately very easy for information to leak from one scenario to another. Static fields are not cleared while the JVM is running. To clear them, you would either have to reset them manually or restart the JVM. Both ways are cumbersome.

How do you share state in Java then? The idiomatic solution in Java is to use dependency injection. That is, inject a common object in each class with steps. An object that is recreated every time a new scenario is executed.

Dependency injection can be done in many ways. A simple solution is to to inject dependencies through the constructor. This is sometimes referred to as Constructor Dependency Injection, CDI.

Cucumber-JVM support many different dependency injection frameworks. One of the least intrusive frameworks is called PicoContainer. It is a minimalistic framework that is invisible everywhere except in the build script where a dependency to it has to be declared.

A small example in two parts

Lets look at a small example and see how it can be implemented with one step definition class and then extend it so the steps are implemented in two different classes with a common object to share state.

The narrative for the example goes like this:

Joanna bought an electric kettle for $100. Unfortunately, it broke after a week

Joanna brings the kettle back to the store and asks for a refund

She has the receipt so there is no problem getting the refund

The store manager Joe refunds her the same value as she payed for the kettle

This example can be documented using Gherkin like this this:

src/test/resources/se/thinkcode/refund-faulty-items.feature

Feature: Refund faulty items
Broken items should be refunded if you have receipt
Scenario: Returning a broken kettle to the store
Given that Joanna bought a faulty kettle for $100
When she return the kettle to the store
And she show her receipt
Then she will get $100 refunded

The runner class can be called anything but the Maven test runner searches the class path for classes that starts or ends with the word test. I prefer classes that ends with the word test. This means that naming it RunCukesTest will allow the test runner to find it and execute it as a part of the regular Maven build. It will be executed during the test phase.

This executable specification will be executed when you do

mvn test

Different concepts

This example talks about two different concepts

Items sold in the store, Kettles for example

Money and rules for refunding

Because it is so small, you would probably not split it yet in a real world implementation.

Splitting the steps

One thing to notice is that the feature file and the runner class should not be touched at all. They are unaffected by the separation of steps.

My first change is to add a dependency to PicoContainer, a dependency injection framework, in the Maven pom.

Next step is to create two classes for the steps. I will call them CustomerSteps and GoodsSteps. The idea is that they will share state between steps that depends on the result of an earlier step in the scenario. Sharing state can be done in different ways, I will use a common world object. This solution is very similar to the one of a shared state with a world object in Ruby. Remember that the world object in Ruby, as well as the shared world injected by PicContainer, are recreated for every scenario. Leakage between scenarios through the world is therefore not possible.

It is as small as I could make it. It only contains a reference to a Customer and a reference to an Item. I decided to make the fields package private to make it easy to refer to them. This is not necessarily the idiomatic way in Java, but it made the changes I had to do to my step implementations small.

The implementations of GoodsSteps and CustomerSteps both contains a constructor that requires an instance of the world object. Since I included PicoContainer, the classes will be created and injected using this constructor with the same instance of world.

Executing this will have the same result as the previous example where the state was shared using instance variables in the step definition class. The difference is that the state shared between the steps now is contained in the world object.

Default constructors

I would have preferred not to create a world and share state through it. It would have been nicer to do the sharing using a Customer and an Item object. This would have been possible if the domain objects I used hadn't required parameters in their constructors.

Conclusion

Using PicoContainer to share state between steps in a scenario is easy and non intrusive. All you need is a constructor that requires an object that PicoContainer can create and inject.

PicoContainer is invisible. Add a dependency to cucumber-picocontainer and make sure that the constructors for the step classes requires an instance of a the same class.

Acknowledgements

I would like to thank Malin Ekholm for proofreading and helping me to find my typos.