BDD Testing with Cucumber, Java and JUnit

December 28th, 2014 by

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.

Running a Cucumber Test with jUnit in Eclipse IDE

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

Example 1: Book Search

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:

Running a Cucumber Test with jUnit in Eclipse IDE

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 Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/cucumber-java-tutorial.git

Resources

Other BDD Articles of mine

The following articles of mine are covering different aspects and frameworks for Behaviour Driven Development:

    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.

    Tags: , , , , , , , , , ,

    23 Responses to “BDD Testing with Cucumber, Java and JUnit”

    1. Wayne Sinclair Says:

      Hi,

      I have tried to replicate this tutorial to understand the concepts, but I am having some challenges. Can anyone please assist me. The last 3 lines in the Library.jave file are giving problems: Here are the 3 lines and the problems:

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

      Problems
      ——–
      Description Resource Path Location Type
      Syntax error on tokens, delete these tokens Library.java /BookClub/src/cucumberfeatures line 22 Java Problem
      Syntax error on tokens, Expression expected instead Library.java /BookClub/src/cucumberfeatures line 22 Java Problem
      Syntax error on token(s), misplaced construct(s) Library.java /BookClub/src/cucumberfeatures line 21 Java Problem
      after cannot be resolved or is not a field Library.java /BookClub/src/cucumberfeatures line 21 Java Problem
      book cannot be resolved Library.java /BookClub/src/cucumberfeatures line 21 Java Problem
      Syntax error on tokens, delete these tokens Library.java /BookClub/src/cucumberfeatures line 20 Java Problem
      book cannot be resolved to a variable Library.java /BookClub/src/cucumberfeatures line 20 Java Problem
      The method stream() is undefined for the type List Library.java /BookClub/src/cucumberfeatures line 20 Java Problem

    2. micha kops Says:

      Hi,

      did you use Java 8? Nevertheless I’ve added a version using Java 7 for you in a branch named jdk7 here: https://bitbucket.org/hascode/cucumber-java-tutorial/src/ed3b7a1a738265ecbbb30f65d363fa3c8c5cd9be/?at=jdk7

      Cheers

      Micha

    3. Robin Says:

      Great stuff! Exactly what the cukes.info site is missing in their documentation. I found it really difficult to get started with cukes/gherkin/junit/java until I read this and now I feel confident to use it, and can happily refer back to cukes.info for more details. Good solid examples that introduce various concepts with tangible simple, but non-trivial, examples.

      Many thanks!

    4. micha kops Says:

      Thanks for your kind remarks! I’m glad that I could be a help for you :)

    5. Srikant Chakravorty Says:

      Thanks for the detailed explanation of the steps. However i am not able to run the tests using ‘mvn test’. Please help in resolving this. Also can you please help me to run it via JUnit.

      The output is as below –
      ——————————————————-
      T E S T S
      ——————————————————-
      There are no tests to run.

      Results :

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

    6. micha kops Says:

      Hi Srikant,

      did you add cucumber-junit as a dependency to your project and have you added @RunWith(Cucumber.class) to your test class?

    7. dahuchao Says:

      Thanks a lot for that post. It was very interesting.

    8. micha kops Says:

      Hi Dahuchao,

      thanks, you’re welcome! :)

    9. John Burrows Says:

      very interesting, but unfortunately the code doesn’t cater for the edge case where the Book is published on the 1st of January in the start year for the range to search.

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

      This corrects for the Start data I did not check the edge case for the end date

    10. Raj Says:

      Crisp and nice article to get a java dev upto pace with Cucumber really fast! Thanks and Love from New York.

    11. Edward Beckett Says:

      I’m working with some Agile user story folks atm … I think I’ll sell them on Cuke pretty quick ;)

    12. Micha Kops Says:

      Thanks, you’re welcome! :)

    13. Vishal Sinha Says:

      Just what I wanted….this was a great tutorial…thank you!

    14. Micha Kops Says:

      Hi Vishal,

      thanks, you’re welcome!

      Cheers

      Micha

    15. Binh Thanh Nguyen Says:

      Thanks, this post helped me a lot

    16. Micha Kops Says:

      Thanks, you’re welcome!

    17. Harshal.S Says:

      Hi Sir,
      i run the Example but it gives me Error:lambda expressions are not supported in -source 1.5
      (use -source 8 or higher to enable lambda expressions).im using jdk 1.8.0_31
      Please help so.
      Thanks in Advance.
      Harshal

    18. Micha Kops Says:

      Hi Harshal.S,

      as I am using Java 8′s streams API in my examples, you need to run my examples with Java 8 and need to tell Maven to use Java 8.
      Please feel free to have a look at the project’s pom.xml (especially the settings for the maven compiler plugin) https://bitbucket.org/hascode/cucumber-java-tutorial/src/382f30ca59d40382a76af575a0e08b6aedccfca6/pom.xml?fileviewer=file-view-default

    19. sumanta Says:

      Hello Micha,

      This is an amazing example for beginners like me on Cucumber-junit.
      Though i could not understand the exact flow so posting below question.
      Please let me know if you get a chance

      BookSearchTest.java class finds feature file by @RunWith(Cucumber.class)
      But how does the BookSearchSteps.java gets trigger to perform all the @given,@when and @then test?

      Thank you very much in advance

    20. Egor Grigorenko Says:

      Great article. It is something to be necessary to start use cucumber with java.

    21. Micha Kops Says:

      Thanks! You’re welcome :)

    22. Kalyan Says:

      Very nice article… simple and concise… Thank you so much

    23. Micha Kops Says:

      Thanks – you’re welcome! :)

    Search
    Categories