DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Low-Code Development: Leverage low and no code to streamline your workflow so that you can focus on higher priorities.

DZone Security Research: Tell us your top security strategies in 2024, influence our research, and enter for a chance to win $!

Launch your software development career: Dive head first into the SDLC and learn how to build high-quality software and teams.

Open Source Migration Practices and Patterns: Explore key traits of migrating open-source software and its impact on software development.

Related

  • Integrate Cucumber in Playwright With Java
  • What BDD Is and Why You Need It: Java and Behavior Driven Development
  • Keep Your Application Secrets Secret
  • How to Activate New User Accounts by Email

Trending

  • Twenty Things Every Java Software Architect Should Know
  • A Look Into Netflix System Architecture
  • DZone's Article Submission Guidelines
  • Applying the Pareto Principle To Learn a New Programming Language
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Effective Java Application Testing With Cucumber and BDD

Effective Java Application Testing With Cucumber and BDD

Increase your testing efficiency by utilizing Cucumber for Java application testing, fully integrated with Behavior-Driven Development (BDD).

By 
Gunter Rotsaert user avatar
Gunter Rotsaert
DZone Core CORE ·
Jun. 04, 24 · Tutorial
Like (3)
Save
Tweet
Share
6.3K Views

Join the DZone community and get the full member experience.

Join For Free

Increase your testing efficiency by utilizing Cucumber for Java application testing, fully integrated with Behavior-Driven Development (BDD). This guide provides comprehensive steps for project setup, scenario writing, step implementation, and reporting.

Introduction

Cucumber is a tool that supports Behavior-Driven Development (BDD). A good starting point in order to learn more about BDD and Cucumber, are the Cucumber guides. BDD itself was introduced by Dan North in 2006, you can read his blog introducing BDD. Cucumber, however, is a tool that supports BDD, this does not mean you are practicing BDD just by using Cucumber. The Cucumber myths is an interesting read in this regard.

In the remainder of this blog, you will learn more about the features of Cucumber when developing a Java application. Do know, that Cucumber is not limited to testing Java applications, a wide list of languages is supported.

The sources used in this blog can be found on GitHub.

Prerequisites

Prerequisites for this blog are:

  • Basis Java knowledge, Java 21 is used;
  • Basic Maven knowledge;
  • Basic comprehension of BDD, see the resources in the introduction.

Project Setup

An initial project can be setup by means of the Maven cucumber-archetype. Change the groupId, artifactId and package to fit your preferences and execute the following command:

Shell
 
$ mvn archetype:generate                      \
   "-DarchetypeGroupId=io.cucumber"           \
   "-DarchetypeArtifactId=cucumber-archetype" \
   "-DarchetypeVersion=7.17.0"               \
   "-DgroupId=mycucumberplanet"               \
   "-DartifactId=mycucumberplanet"               \
   "-Dpackage=com.mydeveloperplanet.mycucumberplanet"  \
   "-Dversion=1.0.0-SNAPSHOT"                 \
   "-DinteractiveMode=false"


The necessary dependencies are downloaded and the project structure is created. The output ends with the following:

Shell
 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.226 s
[INFO] Finished at: 2024-04-28T10:25:16+02:00
[INFO] ------------------------------------------------------------------------


Open the project with your favorite IDE. If you are using IntelliJ, a message is shown in order to install a plugin.

cucumber for Java

Take a closer look at the pom:

  • The dependencyManagement section contains BOMs (Bill of Materials) for Cucumber and JUnit;
  • Several dependencies are added for Cucumber and JUnit;
  • The build section contains the compiler plugin and the surefire plugin. The compiler is set to Java 1.8, change it into 21.
XML
 
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-bom</artifactId>
            <version>7.17.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.junit</groupId>
            <artifactId>junit-bom</artifactId>
            <version>5.10.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
 
<dependencies>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-java</artifactId>
        <scope>test</scope>
    </dependency>
 
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-junit-platform-engine</artifactId>
        <scope>test</scope>
    </dependency>
 
    <dependency>
        <groupId>org.junit.platform</groupId>
        <artifactId>junit-platform-suite</artifactId>
        <scope>test</scope>
    </dependency>
 
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
 
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.13.0</version>
            <configuration>
                <encoding>UTF-8</encoding>
                <source>21</source>
                <target>21</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.5</version>
        </plugin>
    </plugins>
</build>


In the test directory, you will see a RunCucumberTest, StepDefinitions and an example.feature file in the resources section.

The RunCucumberTest file is necessary to run the feature files and the corresponding steps. The feature files and steps will be discussed later on, do not worry too much about it now.

Java
 
@Suite
@IncludeEngines("cucumber")
@SelectPackages("com.mydeveloperplanet.mycucumberplanet")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
public class RunCucumberTest {
}


Run the tests, the output should be successful.

Shell
 
$ mvn test


Write Scenario

When practicing BDD, you will need to write a scenario first. Taken from the Cucumber documentation:

When we do Behavior-Driven Development with Cucumber we use concrete examples to specify what we want the software to do. Scenarios are written before production code. They start their life as an executable specification. As the production code emerges, scenarios take on a role as living documentation and automated tests.

The application you need to build for this blog is a quite basic one:

  • You need to be able to add an employee;
  • You need to retrieve the complete list of employees;
  • You need to be able to remove all employees.

A feature file follows the Given-When-Then (GWT) notation. A feature file consists of:

  • A feature name. It is advised to maintain the same name as the file name;
  • A feature description;
  • One or more scenarios containing steps in the GWT notation. A scenario illustrates how the application should behave.
Plain Text
 
Feature: Employee Actions
  Actions to be made for an employee
 
  Scenario: Add employee
    Given an empty employee list
    When an employee is added
    Then the employee is added to the employee list


Run the tests and you will notice now that the feature file is executed. The tests fail of course, but an example code is provided in order to create the step definitions.

Shell
 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mydeveloperplanet.mycucumberplanet.RunCucumberTest
 
Scenario: Add employee                            # com/mydeveloperplanet/mycucumberplanet/employee_actions.feature:4
  Given an empty employee list
  When an employee is added
  Then the employee is added to the employee list
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.104 s <<< FAILURE! -- in com.mydeveloperplanet.mycucumberplanet.RunCucumberTest
[ERROR] Add an employee.Add employee -- Time elapsed: 0.048 s <<< ERROR!
io.cucumber.junit.platform.engine.UndefinedStepException: 
The step 'an empty employee list' and 2 other step(s) are undefined.
You can implement these steps using the snippet(s) below:
 
@Given("an empty employee list")
public void an_empty_employee_list() {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@When("an employee is added")
public void an_employee_is_added() {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@Then("the employee is added to the employee list")
public void the_employee_is_added_to_the_employee_list() {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
 
        at io.cucumber.core.runtime.TestCaseResultObserver.assertTestCasePassed(TestCaseResultObserver.java:69)
        at io.cucumber.junit.platform.engine.TestCaseResultObserver.assertTestCasePassed(TestCaseResultObserver.java:22)
        at io.cucumber.junit.platform.engine.CucumberEngineExecutionContext.lambda$runTestCase$4(CucumberEngineExecutionContext.java:114)
        at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$5(CucumberExecutionContext.java:136)
        at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23)
        at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:136)
        at io.cucumber.junit.platform.engine.CucumberEngineExecutionContext.runTestCase(CucumberEngineExecutionContext.java:109)
        at io.cucumber.junit.platform.engine.NodeDescriptor$PickleDescriptor.execute(NodeDescriptor.java:168)
        at io.cucumber.junit.platform.engine.NodeDescriptor$PickleDescriptor.execute(NodeDescriptor.java:90)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
 
[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Errors: 
[ERROR]   The step 'an empty employee list' and 2 other step(s) are undefined.
You can implement these steps using the snippet(s) below:
 
@Given("an empty employee list")
public void an_empty_employee_list() {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@When("an employee is added")
public void an_employee_is_added() {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@Then("the employee is added to the employee list")
public void the_employee_is_added_to_the_employee_list() {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
 
[INFO] 
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0


Add Step Definitions

Add the example code from the output above into the StepDefinitions file. Run the tests again. Of course, they fail, but this time a PendingException is thrown indicating that the steps need to be implemented.

Shell
 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mydeveloperplanet.mycucumberplanet.RunCucumberTest
 
Scenario: Add employee                            # com/mydeveloperplanet/mycucumberplanet/employee_actions.feature:4
  Given an empty employee list                    # com.mydeveloperplanet.mycucumberplanet.StepDefinitions.an_empty_employee_list()
      io.cucumber.java.PendingException: TODO: implement me
        at com.mydeveloperplanet.mycucumberplanet.StepDefinitions.an_empty_employee_list(StepDefinitions.java:12)
        at ✽.an empty employee list(classpath:com/mydeveloperplanet/mycucumberplanet/employee_actions.feature:5)
 
  When an employee is added                       # com.mydeveloperplanet.mycucumberplanet.StepDefinitions.an_employee_is_added()
  Then the employee is added to the employee list # com.mydeveloperplanet.mycucumberplanet.StepDefinitions.the_employee_is_added_to_the_employee_list()
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.085 s <<< FAILURE! -- in com.mydeveloperplanet.mycucumberplanet.RunCucumberTest
[ERROR] Add an employee.Add employee -- Time elapsed: 0.032 s <<< ERROR!
io.cucumber.java.PendingException: TODO: implement me
        at com.mydeveloperplanet.mycucumberplanet.StepDefinitions.an_empty_employee_list(StepDefinitions.java:12)
        at ✽.an empty employee list(classpath:com/mydeveloperplanet/mycucumberplanet/employee_actions.feature:5)
 
[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Errors: 
[ERROR]   TODO: implement me
[INFO] 
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0


Implement Application

The first scenario is defined, let’s implement the application. Create a basic EmployeeService which adds the needed functionality. An employee can be added to an employee list which is just a map of employees. The list of employees can be retrieved and the list can be cleared.

Java
 
public class EmployeeService {
 
    private final HashMap<Long, Employee> employees = new HashMap<>();
    private Long index = 0L;
 
    public void addEmployee(String firstName, String lastName) {
        Employee employee = new Employee(firstName, lastName);
        employees.put(index, employee);
        index++;
    }
 
    public Collection<Employee> getEmployees() {
        return employees.values();
    }
 
    public void removeEmployees() {
        employees.clear();
    }
}


The employee is a basic record.

Java
 
public record Employee(String firstName, String lastName) {
}


Implement Step Definitions

Now that the service exists, you can implement the step definitions. It is rather straightforward, you create the service and invoke the methods for the Given-When implementations. Verifying the result is done by Assertions, just as you would do for your unit tests.

Java
 
public class StepDefinitions {
 
    private final EmployeeService service = new EmployeeService();
 
    @Given("an empty employee list")
    public void an_empty_employee_list() {
        service.removeEmployees();
    }
    @When("an employee is added")
    public void an_employee_is_added() {
        service.addEmployee("John", "Doe");
    }
    @Then("the employee is added to the employee list")
    public void the_employee_is_added_to_the_employee_list() {
        assertEquals(1, service.getEmployees().size());
    }
 
}


Run the tests, which are successful now.

Shell
 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mydeveloperplanet.mycucumberplanet.RunCucumberTest
 
Scenario: Add employee                            # com/mydeveloperplanet/mycucumberplanet/employee_actions.feature:4
  Given an empty employee list                    # com.mydeveloperplanet.mycucumberplanet.StepDefinitions.an_empty_employee_list()
  When an employee is added                       # com.mydeveloperplanet.mycucumberplanet.StepDefinitions.an_employee_is_added()
  Then the employee is added to the employee list # com.mydeveloperplanet.mycucumberplanet.StepDefinitions.the_employee_is_added_to_the_employee_list()
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.081 s -- in com.mydeveloperplanet.mycucumberplanet.RunCucumberTest
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0


Extra Scenario

Add a second scenario that tests the removal of employees. Add the scenario to the feature file.

Plain Text
 
Scenario: Remove employees
    Given a filled employee list
    When the employees list is removed
    Then the employee list is empty


Implement the step definitions.

Java
 
@Given("a filled employee list")
public void a_filled_employee_list() {
    service.addEmployee("John", "Doe");
    service.addEmployee("Miles", "Davis");
    assertEquals(2, service.getEmployees().size());
}
@When("the employees list is removed")
public void the_employees_list_is_removed() {
    service.removeEmployees();
}
@Then("the employee list is empty")
public void the_employee_list_is_empty() {
    assertEquals(0, service.getEmployees().size());
}


Tags

In order to run a subset of scenarios, you can add tags to features and scenarios.

Shell
 
@regression
Feature: Employee Actions
  Actions to be made for an employee
 
  @TC_01
  Scenario: Add employee
    Given an empty employee list
    When an employee is added
    Then the employee is added to the employee list
 
  @TC_02
  Scenario: Remove employees
    Given a filled employee list
    When the employees list is removed
    Then the employee list is empty


Run only the test annotated with TC_01 by using a filter.

Shell
 
$ mvn clean test -Dcucumber.filter.tags="@TC_01"
...
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mydeveloperplanet.mycucumberplanet.RunCucumberTest
[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.233 s -- in com.mydeveloperplanet.mycucumberplanet.RunCucumberTest
[INFO] 
[INFO] Results:
[INFO] 
[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1


Reporting

When executing tests, it is often required that appropriate reporting is available. Up till now, only console output has been shown.

Generate an HTML report by adding the following configuration parameter to the RunCucumberTest.

Java
 
@Suite
@IncludeEngines("cucumber")
@SelectPackages("com.mydeveloperplanet.mycucumberplanet")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "html:target/cucumber-reports.html")
public class RunCucumberTest {
}


After running the test, a rather basic HTML report is available in the specified path.

2 passed

Several third-party reporting plugins are available. The cucumber-reporting-plugin offers a more elaborate report. Add the dependency to the pom.

XML
 
<dependency>
    <groupId>me.jvt.cucumber</groupId>
    <artifactId>reporting-plugin</artifactId>
    <version>5.3.0</version>
</dependency>


Enable the report in RunCucumberTest.

Java
 
@Suite
@IncludeEngines("cucumber")
@SelectPackages("com.mydeveloperplanet.mycucumberplanet")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "html:target/cucumber-reports.html")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "me.jvt.cucumber.report.PrettyReports:target/cucumber")
public class RunCucumberTest {
}


Run the tests and in the target/cucumber directory the report is generated. Open the file starting with report-feature.

feature report

Conclusion

Cucumber has great support for BDD. It is quite easy to use and in this blog, you only scratched the surface of its capabilities. An advantage is that you can make use of JUnit and Assertions and the steps can be implemented by means of Java. No need to learn a new language when your application is also built in Java.

application Cucumber (software) Java (programming language) Testing

Published at DZone with permission of Gunter Rotsaert, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Integrate Cucumber in Playwright With Java
  • What BDD Is and Why You Need It: Java and Behavior Driven Development
  • Keep Your Application Secrets Secret
  • How to Activate New User Accounts by Email

Partner Resources


Comments

ABOUT US

  • About DZone
  • Send feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: