Whether behaviour-driven-development, specification by example or acceptance test driven development is the goal, the Cucumber framework eases our life when we need to  establish a link between the non-technical, textual description for a new feature and the tests that prove that the application fulfils these requirements.

In the following short tutorial I’d like to demonstrate how to add Cucumber to a Java project and how to write feature descriptions and test-cases for each step of these descriptions.

cucumber second test in junit runner
Figure 1. Running a Cucumber Test with jUnit in Eclipse IDE

Dependencies

To run the following examples, we need the following three dependencies: junit, cucumber-java and cucumber-junit (using cucumber 1.2.0). Using Maven, adding the following snippet to our pom.xml should add everything we need here.

<dependencies>
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>
	<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>
</dependencies>

A simplified Workflow (Suggestion)

  • Get users, developers, testers, product-owners etc.. together

  • They describe the behaviour of a new feature in plain text and using the Gherkin syntax

  • Developers add the derived feature-files to the project and integrate the cucumber-junit-testrunner

  • Run the tests and watch them fail – cucumber prints snippets for the glue code that can be used for writing the step/glue-classes.

  • Write the code to make the first test (step) pass

  • Repeat until everything is green

Writing the Feature

This is our first feature: A customer shall be able to search books – stored in a file named search_book.feature. Note: Writing possible input parameters in single quotes make it easy for cucumber to generate the glue code for the step files with the correct parameters.

Feature: Book search
  To allow a customer to find his favourite books quickly, the library must offer multiple ways to search for a book.

  Scenario: Search books by publication year
    Given a book with the title 'One good book', written by 'Anonymous', published in 14 March 2013
      And another book with the title 'Some other book', written by 'Tim Tomson', published in 23 August 2014
      And another book with the title 'How to cook a dino', written by 'Fred Flintstone', published in 01 January 2012
    When the customer searches for books published between 2013 and 2014
    Then 2 books should have been found
      And Book 1 should have the title 'Some other book'
      And Book 2 should have the title 'One good book'

Domain Objects

We have to simple classes here in our domain model: library and book.

Book

This is our simple book class: A book has a title, an author an a publication date.

package com.hascode.tutorial.cucumber.book;

import java.util.Date;

public class Book {
	private final String title;
	private final String author;
	private final Date published;

	// constructors, getter, setter ommitted
}

Library

This is our library class: Our library allows us to add books and find books.

package com.hascode.tutorial.cucumber.book;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

public class Library {
	private final List<Book> store = new ArrayList<>();

	public void addBook(final Book book) {
		store.add(book);
	}

	public List<Book> findBooks(final Date from, final Date to) {
		Calendar end = Calendar.getInstance();
		end.setTime(to);
		end.roll(Calendar.YEAR, 1);

		return store.stream().filter(book -> {
			return from.before(book.getPublished()) && end.getTime().after(book.getPublished());
		}).sorted(Comparator.comparing(Book::getPublished).reversed()).collect(Collectors.toList());
	}
}

Adding a Test Runner for jUnit

We need only one annotation to make the stuff work with jUnit: Cucumber as jUnit runner. I have put the steps, the test-class and the feature-file in similar packages so that the cucumbers automatic scan is working but this behaviour can be overwritten with @CucumberOptions e.g. @CucumberOptions(features=\{“path-to-features”}..).

package feature.book;

import org.junit.runner.RunWith;

import cucumber.api.junit.Cucumber;

@RunWith(Cucumber.class)
public class BookSearchTest {
}

Adding Steps

This is our glue code containing the step definitions. It’s a stateful class that matches steps from the feature description with their parameters to states in our application. The @Format annotation allows us to convert dates in a specific date format into a date parameter.

package feature.book;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.hascode.tutorial.cucumber.book.Book;
import com.hascode.tutorial.cucumber.book.Library;

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

public class BookSearchSteps {
	Library library = new Library();
	List<Book> result = new ArrayList<>();

	@Given(".+book with the title '(.+)', written by '(.+)', published in (.+)")
	public void addNewBook(final String title, final String author, @Format("dd MMMMM yyyy") final Date published) {
		Book book = new Book(title, author, published);
		library.addBook(book);
	}

	@When("^the customer searches for books published between (\\d+) and (\\d+)$")
	public void setSearchParameters(@Format("yyyy") final Date from, @Format("yyyy") final Date to) {
		result = library.findBooks(from, to);
	}

	@Then("(\\d+) books should have been found$")
	public void verifyAmountOfBooksFound(final int booksFound) {
		assertThat(result.size(), equalTo(booksFound));
	}

	@Then("Book (\\d+) should have the title '(.+)'$")
	public void verifyBookAtPosition(final int position, final String title) {
		assertThat(result.get(position - 1).getTitle(), equalTo(title));
	}
}

Example 2: Salary Manager

In the following example, we’ll be using cucumbers data table import to map data from an ASCII table into our domain model.

Writing the Feature

Again we’re starting with a description of the desired behaviour. To enter multiple employees, we’re using a data table as shown here:

Feature: Salary Management

  Scenario: Modify an employee's salary
    Given the salary management system is initialized with the following data
      | id  | user      | salary   |
      | 1   | donald    | 60000.0  |
      | 2   | dewie     | 62000.0  |
      | 3   | goofy     | 55000.0  |
      | 4   | scrooge   | 70000.0  |
      | 5   | daisy     | 56000.0  |
      | 6   | minnie    | 62000.0  |
      | 7   | mickey    | 51000.0  |
      | 8   | fethry    | 66500.0  |
    When the boss increases the salary for the employee with id '3' by 5%
    Then the payroll for the employee with id '3' should display a salary of 57750

Domain Objects

Again we have two classes: employees and the salary manager.

Employee

An employee has an id, a user-name and a salary, encapsulated in this simple POJO:

package com.hascode.tutorial.cucumber.salary;

public class Employee {
	private int id;
	private String user;
	private float salary;

	// constructor, getter, setter ommitted
}

SalaryManager

The salary manager allows to increase an employee’s salary and to find an employee by his id. The list of know employees is passed as constructor parameters so that the simple for-tutorials-only implementation looks like this:

package com.hascode.tutorial.cucumber.salary;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class SalaryManager {
	private Map<Integer, Employee> employees = new HashMap<>();

	public SalaryManager(final List<Employee> employees) {
		this.employees = employees.stream().collect(Collectors.toMap(Employee::getId, Function.<Employee> identity()));
	}

	public void increaseSalary(final Integer id, final int increaseInPercent) {
		Employee nominee = employees.get(id);
		float oldSalary = nominee.getSalary();
		nominee.setSalary(oldSalary + oldSalary * increaseInPercent / 100);
	}

	public Employee getPayroll(final int id) {
		return employees.get(id);
	}
}

Adding a Test Runner for jUnit

Again we’re adding this starting point for jUnit:

package feature.salary;

import org.junit.runner.RunWith;

import cucumber.api.junit.Cucumber;

@RunWith(Cucumber.class)
public class SalaryTest {

}

Adding Steps

We’re running the tests, see them fail, have a look in the console and copy and paste the generated cucumber code. Afterwards we’re implementing the rest until we seen the green light ;)

An interesting feature is the ability to map the data table into a collection of a mappable element. The first method annotated with @Given receives a generic list of employees as a parameter  so that the field of the data table are mapped to their equivalent fields in the employee class.

package feature.salary;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;

import java.util.List;

import com.hascode.tutorial.cucumber.salary.Employee;
import com.hascode.tutorial.cucumber.salary.SalaryManager;

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

public class SalarySteps {
	SalaryManager manager;

	@Given("^the salary management system is initialized with the following data$")
	public void the_salary_management_system_is_initialized_with_the_following_data(final List<Employee> employees) throws Throwable {
		manager = new SalaryManager(employees);
	}

	@When("^the boss increases the salary for the employee with id '(\\d+)' by (\\d+)%$")
	public void the_boss_increases_the_salary_for_the_employee_with_id_by(final int id, final int increaseInPercent) throws Throwable {
		manager.increaseSalary(id, increaseInPercent);
	}

	@Then("^the payroll for the employee with id '(\\d+)' should display a salary of (\\d+)$")
	public void the_payroll_for_the_employee_with_id_should_display_a_salary_of(final int id, final float salary) throws Throwable {
		Employee nominee = manager.getPayroll(id);
		assertThat(nominee.getSalary(), equalTo(salary));
	}
}

Directory Structure

The directory structure with the examples described above looks  like this one:

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── hascode
    │   │           └── tutorial
    │   │               └── cucumber
    │   │                   ├── book
    │   │                   │   ├── Book.java
    │   │                   │   └── Library.java
    │   │                   └── salary
    │   │                       ├── Employee.java
    │   │                       └── SalaryManager.java
    │   └── resources
    └── test
        ├── java
        │   └── feature
        │       ├── book
        │       │   ├── BookSearchSteps.java
        │       │   └── BookSearchTest.java
        │       └── salary
        │           ├── SalarySteps.java
        │           └── SalaryTest.java
        └── resources
            └── feature
                ├── book
                │   └── search_book.feature
                └── salary
                    └── salary_management.feature

Running the Tests

Now we’re ready to run the tests – using Maven it’s done like this:

$ mvn test
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running feature.book.BookSearchTest

1 Scenarios (1 passed)
7 Steps (7 passed)
0m0.122s

Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.375 sec
Running feature.salary.SalaryTest

1 Scenarios (1 passed)
3 Steps (3 passed)
0m0.018s

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.033 sec

Results :

Tests run: 12, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Otherwise we can use our favourite IDE’s jUnit Runner – this is what running the tests looks like in Eclipse IDE:

cucumber second test in junit runner
Figure 2. Running a Cucumber Test with jUnit in Eclipse IDE

Cucumber and Java EE

Integrating Cucumber in a Java EE project is quite easy when using Arquillian and the Cukespace extension – please feel free to have a look at another article of mine for further reading: "Marrying Java EE and BDD with Cucumber, Arquillian and Cukespace".

Alternative: jBehave

JBehave is an alternative framework for writing BDD tests using the Gherkin syntax an with annotation driven mapping between the text format of a story and the glue code to make the tests work. I’ve written an article a while ago about this framework so please feel free to have a look if interested: "Oh JBehave, Baby! Behaviour Driven Development using JBehave".

Tutorial Sources

Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:

git clone https://github.com/hascode/cucumber-java-tutorial.git

Other BDD Articles of mine

Article Updates

  • 2015-01-07: Link to my article about Cucumber, Java EE and Cukespace added.

  • 2017-04-06: Links to other BDD articles of mine added.