Category Archives: Gherkin

Duplicate step definitions in Gherkin & Cucumber (using IntelliJ)

Continuing from my last post Gherkin & Cucumber in Java (using IntelliJ).

Let’s assume you have a project set-up as per my previous post, delete any existing feature files and let’s create two very simple and very similar feature files

Feature: Calculator Add

  Scenario: Add two numbers
    Given Two input values, 1 and 2
    When I add the two values
    Then I expect the result 3
Feature: Calculator Subtract

  Scenario: Subtract two numbers
    Given Two input values, 1 and 2
    When I subtract the two values
    Then I expect the result -1

When we start creating our step definition files we might start with the Add feature and create something like AddStepdefs.java which includes the following code

package com.putridparrot;

import cucumber.api.PendingException;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

public class AddStepdefs {
    @Given("^Two input values, (-?\\d+) and (-?\\d+)$")
    public void twoInputValuesAnd(int a, int b) throws Throwable {
    }

    @When("^I add the two values$")
    public void iAddTheTwoValues() throws Throwable {
    }

    @Then("^I expect the result (-?\\d+)$")
    public void iExpectTheResult(int r) throws Throwable {
    }
}

Next we might start creating the step definitions file for the Subtract feature and notice, IntelliJ shows one step as already existing (i.e. it’s not got a highlight background colour). If you press the CTRL key and move the mouse over the step which appear to exist already (i.e. Two input values, 1 and 2) a hyperlink line will appear, click this and it will take you to the previously added step in the AddStepdefs file.

What’s happening here is that whilst we might define separate classes per feature (which may well seem a logical way to write our test code/step definitions), Cucumber is actually matching to methods based upon the RegEx within the annotations, i.e. Given Two input values, 1 and 2 maps to

@Given("^Two input values, (-?\\d+) and (-?\\d+)$")
public void twoInputValuesAnd(int a, int b) throws Throwable {
}

Cucumber doesn’t actually take notice of the Given/When/Then annotations for matching the method to the line of Gherkin code.

Let us assume that we simply copy the missing step into the SubtractStepdefs.java file, we now have duplicate step definitions according to Cucumber, which is ofcourse correct if we think that each step is in essence globally scoped by Cucumber.

Or to put it another way, Cucumber will search through all the packages within the “Glue” package(s) to locate matching RegEx’s. If it finds more than one matching RegEx we get a duplicate step error. Here’s the (truncated) error that Cucumber will display for us when we try to run all features.

Exception in thread "main" cucumber.runtime.DuplicateStepDefinitionException: Duplicate step definitions in com.putridparrot.SubtractStepdefs.twoInputValuesAnd(int,int)

Handling duplicate step definitions

So how do we resolve a situation where we want to run all features and we have duplicate steps?

The easiest solution is, ensure you never have duplicate steps unless you intended to reuse the same step definition code – the general idea is to think of the language used to define a step in Gherkin as a specific task and another step using the same text is really the same task (at least when associated with the same package).

Ofcourse we can have duplicate steps in different packages, we just need to ensure we run features against on that package using the Glue option, i.e. do not reference all packages but just point to the one’s specific to the features.

If we therefore have the code like this in AddStepdefs and not in SubtractStepdefs

public class AddStepdefs {
   private int value1;
   private int value2;

   @Given("^Two input values, (-?\\d+) and (-?\\d+)$")
   public void twoInputValuesAnd(int a, int b) throws Throwable {
      value1 = a;
      value2 = b;  
   }
   // other code removed
}

the we have created another problem. The instance variables, value1 and value2 or stored in AddStepdefs and hence we’d also have similar variables stored in SubtractStepdefs (for use with the subtract scenario) however, the twoInputValuesAnd will never change the instance variables in the SubtractStepdefs for obvious reasons…

So how do we share instance data across our step definition files?

As programmers we might see a Scenario as analogous to a class but you can see that to ensure we adhere to the DRY principle we’d actually be better off creating a single class with all step definitions across all scenarios. This ofcourse is also not ideal because once we start to rack up a large number of scenarios, our class will become large and unwieldy.

So what we really want to do is create an instance of some shared state and have Cucumber pass this to each step definition class.

This is where cucumber-picocontainer comes in. If we add the following to the pom.xml

<dependency>
   <groupId>info.cukes</groupId>
   <artifactId>cucumber-picocontainer</artifactId>
   <version>1.2.5</version>
   <scope>test</scope>
</dependency>

and can “context” like this

public class ScenarioContext {
    private int value1;
    private int value2;
    private int result;

    public int getValue1() {
        return value1;
    }

    public void setValue1(int value) {
        this.value1 = value;
    }

    public int getValue2() {
        return value2;
    }

    public void setValue2(int value) {
        this.value2 = value;
    }

    public int getResult() {
        return result;
    }

    public void setResult(int value) {
        this.result = value;
    }
}

We can now change our step definitions files to look like this, first the AddStepdefs file

public class AddStepdefs {

    private ScenarioContext context;

    public AddStepdefs(ScenarioContext context) {
        this.context = context;
    }

    @Given("^Two input values, (-?\\d+) and (-?\\d+)$")
    public void twoInputValuesAnd(int a, int b) throws Throwable {
        context.setValue1(a);
        context.setValue2(b);
    }

    @When("^I add the two values$")
    public void iAddTheTwoValues() throws Throwable {
        Calculator calculator = new Calculator();

        context.setResult(calculator.add(context.getValue1(), context.getValue2()));
    }

    @Then("^I expect the result (-?\\d+)$")
    public void iExpectTheResult(int r) throws Throwable {
        Assert.assertEquals(r, context.getResult());
    }
}

and the SubtractStepdefs without any duplicated steps could look like this

public class SubtractStepdefs {

    private ScenarioContext context;

    public SubtractStepdefs(ScenarioContext context) {
        this.context = context;
    }

    @When("^I subtract the two values$")
    public void iSubtracTheTwoValues() throws Throwable {
        Calculator calculator = new Calculator();

        context.setResult(calculator.subtract(context.getValue1(), context.getValue2()));
    }
}

When a feature file is run, Cucumber will get an instance of our ScenarioContext passed to each class by the picocontainer which will them be used by the class methods.

Gherkin & Cucumber in Java (using IntelliJ)

This post will be based around implementing features etc. using IntelliJ, so your experience with tooling may differ from those experiences outlined in this post.

If you’re coming from Specflow and C# to IntelliJ and Java

I’ve used Gherkin previously when using Specflow in Visual Studio and with C#. The concepts, syntax etc. are exactly the same, however be aware that the generated code isn’t just different in terms of the language used (i.e. C# and Java for example) but also in the RegEx generated. As an example if your Gherkin has numbers then Specflow generated code allows for negatives, whereas the code generated within the IntelliJ/Java systems is a little stricter with it’s RegEx and negatives need to be added to the RegEx.

Creating our demo project

  • File | New Project
  • Select Maven project type
  • Enter the GroupId and ArtifactId

Now open the pom.xml and add the following

<dependencies>
   <dependency>
      <groupId>info.cukes</groupId>
      <artifactId>cucumber-java</artifactId>
      <version>1.2.5</version>
      <scope>test</scope>
   </dependency>
   <dependency>
      <groupId>info.cukes</groupId>
      <artifactId>cucumber-jvm</artifactId>
      <version>1.2.5</version>
      <type>pom</type>
   </dependency>
   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
   </dependency>
</dependencies>

Creating our code

Let’s create a simple little class to run our tests against. Within src/java add a new class, mine’s named com.putridparrot.Calculator. Here’s the code that’s stored in src/main/java/com.putridparrot package

package com.putridparrot;

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

In src/test create a folder named resources and right mouse click on this folder and select Mark Directory As | Test Resources Root.

Note: the resources folder is not a requirement, more a convention.

Next, add a folder named features and within this folder create a new file named add.feature, this is a really simple example feature

Note: this post is not about writing good feature’s so this example is extremely basic.

Feature: Calculator
  
  Scenario Outline: add two numbers
    Given Two input values, <first> and <second>
    When I add the two values
    Then I expect the result <result>

  Examples:
    | first | second | result |
    | 1     | 12     | 13     |
    | -1    | 6      | 5      |

Select the end of each the Given, When and Then line and press alt+enter for each and select create steps definition. Name the file AddStepDef (mine’s created in the src/test/java/com/putridparrot folder/package) and set the file type to Java when you create your first step definition, then subsequently add step definitions to this same file.

Note: At the time of writing, selecting create all step definitions, does not seem to work as expected and simply creates a single step, not yet sure why.

Here’s what my generated steps looked like

package com.putridparrot;

import cucumber.api.PendingException;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

public class AddStepDefs {
    @Given("^Two input values, <first> and <second>$")
    public void twoInputValuesFirstAndSecond() throws Throwable {
        // Write code here that turns the phrase above into concrete actions
        throw new PendingException();
    }

    @When("^I add the two values$")
    public void iAddTheTwoValues() throws Throwable {
        // Write code here that turns the phrase above into concrete actions
        throw new PendingException();
    }

    @Then("^I expect the result <result>$")
    public void iExpectTheResultResult() throws Throwable {
        // Write code here that turns the phrase above into concrete actions
        throw new PendingException();
    }
}

Sadly these require editing as the placeholders (the <> wrapped text) are not being passed as arguments into our methods. The simplest way to solve this is to change the placeholders to numbers (or the type we expect) delete the methods from the step definition file and then regenerate the step definitions. So here’s my “temporary” change to the feature to get valid steps generated.

Feature: Calculator

  Scenario Outline: add two numbers
    Given Two input values, 1 and 2
    When I add the two values
    Then I expect the result 3

    Examples:
      | first | second | result |
      | 1     | 12     | 13     |
      | -1    | 6      | 5      |

this gives us the following code

package com.putridparrot;

import cucumber.api.PendingException;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

public class AddStepDefs {


    @Given("^Two input values, (\\d+) and (\\d+)$")
    public void twoInputValuesAnd(int arg0, int arg1) throws Throwable {
        // Write code here that turns the phrase above into concrete actions
        throw new PendingException();
    }

    @When("^I add the two values$")
    public void iAddTheTwoValues() throws Throwable {
        // Write code here that turns the phrase above into concrete actions
        throw new PendingException();
    }
    
    @Then("^I expect the result (\\d+)$")
    public void iExpectTheResult(int arg0) throws Throwable {
        // Write code here that turns the phrase above into concrete actions
        throw new PendingException();
    }

}

Return the feature file back to the version with placeholders.

Before we write our actual test code, let’s run the feature tests. In IntelliJ we can right mouse click on an individual feature file or the folder with the features and select Run feature or Run all features.

As we only have one feature file at the moment, select it, right mouse click and select Run: Feature Calculator, you’ll probably be hit with output in the test window along the lines of

Undefined step: Given… etc.

We need to edit the configuration for the runner and supply the Glue (in my case I have the steps in the package com.putridparrot, so I enter that in the Glue: option). Now running the feature should result in another error

Undefined step: Given Two input values, -1 and 6

As you can see, there’s a problem with the generated code as it expects a method that handles a negative number and none exist, we need to amend our RegEx for all methods to include a possible – sign. Changed all (\\d+) to (-?\\d+) now running the feature will have fixed the error.

Writing our test code

We can now write fairly standard unit testing code into the step definition file (in this case I’m using JUnit), so here’s a simple example

package com.putridparrot;

import cucumber.api.java.Before;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import org.junit.Assert;

public class AddStepDefs {

    private Calculator calculator;
    private int value1;
    private int value2;
    private int result;

    @Before
    public void before() {
        calculator = new Calculator();
    }

    @Given("^Two input values, (-?\\d+) and (-?\\d+)$")
    public void twoInputValuesAnd(int arg0, int arg1) throws Throwable {
        value1 = arg0;
        value2 = arg1;
    }

    @When("^I add the two values$")
    public void iAddTheTwoValues() throws Throwable {
        result = calculator.add(value1, value2);
    }

    @Then("^I expect the result (-?\\d+)$")
    public void iExpectTheResult(int arg0) throws Throwable {
        Assert.assertEquals(arg0, result);
    }
}

Common mistakes

Glue, glue and more glue – make sure you set up the glue.

The Gherkin language

Gherkin is a DSL used within BDD development. It’s used along with Cucumber which processes the DSL or in the case of .NET we can use tools such as SpecFlow (which I posted about a long time back, see Starting out with SpecFlow) to help generate files and code.

Gherkin allows us to create the equivalent of use cases in a human readable form, using a simple set of keywords and syntax which can then be use to generate a series of method calls to undertake some action and assertion.

Getting started

We can use a standard text editor to create Gherkin’s feature files, but if you prefer syntax highlighting and intellisense (although this language we’re using is pretty simple) then install SpecFlow into Visual Studio or add a Gherkin syntax highlighter into VSCode (for example) or just use your preferred text editor.

I’m going to create a feature file (with the .feature extension) using the SpecFlow item template, so we have a starting point which we can then work through. Here’s the file generated code

Feature: Add a project
	In order to avoid silly mistakes
	As a math idiot
	I want to be told the sum of two numbers

@mytag
Scenario: Add two numbers
	Given I have entered 50 into the calculator
	And I have entered 70 into the calculator
	When I press add
	Then the result should be 120 on the screen

What we have here is a Feature which is meant to describe a single piece of functionality within an application. The feature name should be on the same line as the Feature: keyword.

In this example, we’ve defined a feature which indicates our application will have some way to add a project within our application. We can now add an optional description, which is exactly what the SpecFlow template did for us. The Description may span multiple lines (as can be seen above with the lines under the Feature line being the description) and should be a brief explanation of the specific feature or use case. Whilst the aim is to be brief, it should include acceptance criteria and any relevant information such as user permissions, roles or rules around the feature.

Let’s change our feature text to be a little meaningful to our use case.

Feature: Add a project

	Any user should be able to create/add a new project
	as long as the following rules are met

	1. No duplicate project names can exist
	2. No empty project names should be allowed

I’m sure with a little thought I can come up with more rules, but you get the idea.

The description of the feature is a useful piece of documentation and can be used as a specification/acceptance criteria.

Looking at the generated feature code, we can see that SpecFlow also added @mytag. Tags allow us to group scenarios. In terms of our end tests, this can be seen as a way of grouping features, scenarios etc. Multiple tags may be applied to a single feature or scenario etc. for example

@project @mvp
Feature: Add a project

I don’t need any tags for the feature I’m implementing here, so I’ll delete that line of code.

The Scenario is where we define each specific scenario of a feature and the steps to be taken/expected. The Scenario takes a similar form to a Feature, i.e. Scenario: and then a description of the context.

Following the Scenario line, we then begin to define the steps that make up our scenario, using the keywords Given, When, Then, And and But.

In unit testing usage, we can view Given as a precondition or setup. When, And and But as actions (where But is seen as a negation) and this leaves Then as an assertion.

Let’s just change to use some of these steps in a more meaningful manner within the context of our Scenario

Scenario: Add a project to the project list
	Given I have added a valid project name
	When I press the OK button
	Then the list of projects should now include my newly added project

Eventually, if we generate code from this feature file, each of these steps would get turned into a set of methods which could be used as follows

  • Given becomes an action to initialize the code to the expected context
  • When becomes an action to set-up any variables, etc.
  • Then becomes an assertion or validate any expectations

Multiple When‘s can be defined using the And keyword, for example imagine our Scenario looked like this

Scenario: Add a project to the project list
	Given I have added a valid project name
	When I press the OK button
	And I checked the Allow checkbox
	Then the list of projects should now include my newly added project

Now in addition to the When I press the OK button step I would also get another When created, as the And keyword simply becomes another When action. In essence the And duplicates the previous keyword.

We can also include the But keyword. As we’ve seen And, this is really another way of defining a additional When steps but in a more human readable way, But works in the same way as the And keyword by simply creating another When step in generated code, however But should be viewed as a negation step, for example

Scenario: Add a project to the project list
	Given I have added a valid project name
	When I press the OK button
	But the Do Not Allow checkbox is unchecked
	Then the list of projects should now include my newly added project

Finally, as stated earlier, Then can be viewed as a place to write our assertions or simply check if the results matches our expectations. We can again use And after the Then to create multiple then steps and thus assert multiple expectations, for example

Scenario: Add a project to the project list
	Given I have added a valid project name
	When I press the OK button
	But the Do Not Allow checkbox is unchecked
	Then the list of projects should now include my newly added project
        And the list of project should increase by 1

More keywords

In the previous section we covered the core keywords of Gherkin for defining our features and scenarios. But Gherkin also includes the following

Background, Scenario Outline and Examples.

The Background keyword is used to define reusable Given steps, i.e. if all our scenarios end up requiring the application to be in edit mode we might declare a background before any scenarios, such as this

Background:
	Given the projects list is in edit mode
	And the user clicks the Add button

we’ve now created a sort of, top level scenario which is run before each Scenario.

The Scenario Outline keywords allow us to define a sort of scenario function or template. So, if we have multiple scenarios which only differ in terms of data being used, then we can create a Scenario Outline and replace the specific data points with variables.

For example let’s assume we have scenarios which actually define multiple project names to fulfil the feature’s two rules (we outlined in the feature). Let’s assume we always have a project named “Default” within the application and therefore we cannot duplicate this project name. We also cannot enter a “” project name.

If we write these as two scenarios, then we might end up with the following

Scenario: Add a project to the project list with an empty name
	Given the project name ""
	When I press the OK button
	Then the project should not be added

Scenario: Add a project to the project list with a duplicate name
	Given the project name "Default"
	When I press the OK button
	Then the project should not be added

If we include values within quotation marks or include numbers within our steps, then these will become arguments to the methods generated for these steps. This obviously offers us a way to reuse such steps or use example data etc.

Using a Scenario Outline these could be instead defined as

Scenario Outline: Add a project to the project list with an invalid name
	Given the project name <project-name>
	When I press the OK button
	Then the project should not be added

	Examples: 
	| project-name |
	| ""           |
	| "Default"    |

The <> acts as placeholders and the string within can be viewed as a variable name. We then define Examples which becomes our data inputs to the scenario.

Gherkin also includes the # for use to start a new line and mark it as a comment, multiple lines may be commented out using triple quotation marks, such as “””. Here’s a example of usage

# This is a comment
	
Scenario Outline: Add a project to the project list  with an invalid name
	Given the project name <project-name>
	"""
	Given the project name <project-id>
	"""
	When I press the OK button
	Then the project should not be added

	Examples: 
	| project-name |
	| ""           |
	| "Default"    |

It should be noted that after listing these different keywords and their uses you can also create a scenario that’s a Given followed by a Then, in other words a setup step followed by an assertion, if this is all you need.

For example

Scenario: Add a project
	Given A valid project name
        Then the project list should increase by 1

SpecFlow specifics

On top of the standard Gherkin keywords, SpecFlow adds a few bits.

The @ignore tag is used by SpecFlow to generate ignored test methods.

SpecFlow has also add Scenario Template as a synonym to Scenario Outline. Like wise Scenarios is a alternate to Examples.

Code generation

We’re not going to delve into Cucumber or the SpecFlow generated code except to point out that if you define more than one step within a scenario with the same text, this will generate a call to the same method in code. So whilst you might read a scenario as if it’s a new context or the likes, ultimately the code generated will execute the same method.

References

Gherkin Reference
Using Gherkin Language In SpecFlow