Getting Started with Cucumber

Learn to use Cucumber, Gherkin, Hamcrest and Rest Assured to integrate Behavior-Driven Development (BDD) in an application made using Spring Boot and Kotlin. By Prashant Barahi.

Leave a rating/review
Download materials
Save for later
Share

The Software Development Life Cycle (SDLC) generally involves two kinds of people: business professionals and engineers. Because their expertise is in different domains, the business requirements can easily be misunderstood or expressed unclearly and the final product might fail to meet the business needs. Behavior-Driven Development (BDD) is a software development process that encourages:

  • Collaboration among the whole team.
  • Use of ubiquitous language to define the behavior of the system, i.e. a specification.
  • Use of automated tests to validate the system against the specification.

The progression and the behavior of the system remain transparent across the team throughout the project’s lifecycle, ensuring a shared understanding of the problem to solve.

So a specification written in plain text that can be picked up by a testing framework and then executed? How do you make something like JUnit do that? Enter Cucumber! Cucumber reads specifications and validates what the software does against what the specification states. It helps you integrate BDD into your SDLC. Cucumber is available for many programming languages and the specifications can be written in various spoken languages.

In this tutorial, you’ll:

  • Use Gherkin to define the specifications.
  • Integrate Cucumber in a Spring Boot application.
  • Use assertion libraries like Hamcrest and Rest Assured to validate specifications.
  • Learn how to share state between steps.
  • Use multiple threads to execute tests in parallel.
  • Learn how to deal with flaky tests.
  • Generate test reports.

You’ll work with an application called artikles that’s made with Spring Boot and Kotlin.

Note: This article assumes you’re familiar with the basics of Spring Boot and Hibernate.

Getting Started

Click Download Materials at the top or bottom of the tutorial to download the starter project. Fire up the IntelliJ IDEA and select Open…. Then, navigate to and open the starter project.

Build and run the app. You’ll find the server running on port 8080.

Now, create the first article:

curl -X POST http://localhost:8080/articles -d '{"title":"Test","body":"Hello, World!"}' -H "Content-Type:application/json"
	

And send a GET request:

curl http://localhost:8080/articles
	

You’ll get the article you just created as a response. Great! Now that your application is up and running, fire up your browser and go to localhost:8080/h2-console. Log in using the following configurations:

H2 Console Login screen with the configuration values from application.properties

You can find these configurations in application.properties, located at src/main/resources. This application uses an in-memory H2 database. Keep in mind that stopping and restarting the application will reset the database. The starter project doesn’t contain any test cases yet. In the upcoming sections, you’ll learn how to integrate tests in this project. Before that, Gherkin!

Gherkin

The executable specification isn’t really “plain” text. It’s written in a structured format called Gherkin. Gherkin provides a set of grammar rules and keywords to describe the specification. The specifications live in *.feature files and contain one or more scenarios. Each scenario contains a series of steps Cucumber executes and validates against the business expectations.

A typical specification looks like this:

Feature: Bank transfer.

  Scenario: Money should be transferred from applicant account to beneficiary account.
	Given Account "001" has balance of "$100".
	And Account "002" has balance of "$1000".
	When Amount of "$50" is transferred from account "001" to account "002".
	Then Account "001" should have balance of "$50".
	And Account "002" should have balance of "$1050".
	

Gherkin provides:

  • Feature to group related scenarios.
  • Scenario or Example to group a series of steps.
  • Given, When, Then, And and But to describe steps. These steps are executed sequentially.
  • Scenario Outline to run the same Scenario multiple times using different combinations of inputs.

You can provide a list of inputs to a step definition using Data Tables:

| account |   balance   |
|   001   |     $100    |
|   002   |     $150    |
|   004   |     $1000   |
	

Refer to the Gherkin documentation to learn about the keywords and their usage. Next, you’ll learn about Cucumber.

Cucumber

Cucumber connects Gherkin steps to a step definition. A step definition is a method that’s annotated with one of the step keywords: (@Given, @When, @Then or @But). It contains either a Regular Expression or a Cucumber Expression that links the method to the Gherkin steps.

A step definition for the scenario above could look like this:

import io.cucumber.java.en.*

class StepsDefinition {

  @Given("Account {string} has balance of {string}")
  fun setUpAccountWithBalance(account: String, balance: String) {
    // Account "001" has balance of "$100".
    // Account "002" has balance of "$1000".

    val money: Money = Money.from(balance)
    // setup account with [money]
  }

  @When("Amount of {string} is transferred from account {string} to account string")
  fun transferAmount(balance: String, fromAccountNumber: String, toAccountNumber: String) {
    // Amount of "$50" is transferred from account "001" to account "002".

    val fromAccount = getAccount(fromAccountNumber)
    val toAccount = getAccount(toAccountNumber)
    // Transfer balance
    TransferService.transfer(from=fromAccount, to=toAccount, amount=balance)
  }

  @Then("Account {string} should have balance of {string}.")
  fun validateAmount(accountNumber: String, balance: String) {
    // Account "001" should have balance of "$50".
    // Account "002" should have balance of "$1050".

    val account =  getAccount(accountNumber)
    assertEquals(balance, account.balance)
  }

  // ...
}
	

The step definition methods either initialize or set up a state or validate the current state of the system.

Cucumber Expressions support basic parameter types like {int}, {float}, {string}, {biginteger}, {double}, {long} and {word}. You can learn more about them in the Cucumber Expressions documentation.

Now that you know about Cucumber, you’ll learn to integrate it in the Spring Boot project.

Setting up Cucumber

First, add the following dependencies to build.gradle:

// 1
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.rest-assured:rest-assured:4.4.0")

// 2
testImplementation("io.cucumber:cucumber-java:6.10.4")
// 3
testImplementation("io.cucumber:cucumber-spring:6.10.4")
// 4
testImplementation("io.cucumber:cucumber-junit-platform-engine:6.10.4")

// 5
testRuntimeOnly("org.junit.platform:junit-platform-console")
	

So, what’s going on here?

  1. Cucumber is un-opinionated toward the assertion library, so you can use which one you prefer. You’ll use Rest Assured to simulate the HTTP endpoints and Hamcrest to assert the HTTP response.
  2. This adds Cucumber’s core classes and annotations to the test scope.
  3. cucumber-spring is required because the application needs to start and be ready to accept HTTP requests before executing any of the steps.
  4. cucumber-junit-platform-engine is the engine JUnit Console Launcher uses to execute Cucumber’s scenario.
  5. Use the Console Launcher to launch the JUnit Platform and execute test cases from the console.

In build.gradle, replace the tasks.withType block with the following:

tasks {
  val consoleLauncherTest by registering(JavaExec::class) {
    dependsOn(testClasses)
    classpath = sourceSets["test"].runtimeClasspath
    mainClass.set("org.junit.platform.console.ConsoleLauncher")
    args("--include-engine", "cucumber")
    args("--details", "tree")
    args("--scan-classpath")
    // Pretty prints the output in console
    systemProperty("cucumber.plugin", "pretty")
    // Hides Cucumber ads
    systemProperty("cucumber.publish.quiet", true)
  }

  test {
    dependsOn(consoleLauncherTest)
    exclude("**/*")
  }
}
	

This configured the JUnit Console Launcher with the cucumber-junit-platform-engine to execute Cucumber’s scenarios.

Perform Gradle sync and execute that task using ./gradlew test on Unix machines or gradlew.bat test on Windows. You should see a gradle task run and then a block of text that looks something like this:

Test run finished after 147 ms
[         1 containers found      ]
[         0 containers skipped    ]
[         1 containers started    ]
[         0 containers aborted    ]
[         1 containers successful ]
[         0 containers failed     ]
[         0 tests found           ]
[         0 tests skipped         ]
[         0 tests started         ]
[         0 tests aborted         ]
[         0 tests successful      ]
[         0 tests failed          ]

Last, go to Preferences > Plugins and check that you installed the necessary plugins for Cucumber and enabled them in IntelliJ. You may have to restart IntelliJ after installing the plugins. These plugins help to make the testing flow much easier when using Cucumber.

Enable the IDE plugins for Gherkin, Cucumber for Groovy and Cucumber for Java

You’re now ready to integrate Cucumber in the Spring Boot. Before that, take a brief tour of Rest Assured.