Back to Spring Boot Course

Spring Boot Course

BDD is a software development process that involves collaboration between developers, testers, and non-technical stakeholders. It encourages writing tests in a natural language format that is easily understood by all parties involved. The goal of BDD is to create a shared understanding of the system's behavior.

In BDD, Cucumber helps automate the acceptance criteria for features and user stories, using Gherkin syntax to describe the behavior of the application in a natural, readable way.

While Cucumber is mainly a BDD tool, it can also be used for integration testing. In this case, it tests how the components of your system interact with each other and verifies if the entire system behaves as expected.

Integration Testing using Cucumber

  • In an integration test context, Cucumber can test whether various components (like APIs, database interactions, and services) work together to fulfill the application's business requirements.
  • For instance, you can use Cucumber to test that your REST API behaves correctly by sending HTTP requests and asserting the response values, thus verifying that the various layers (e.g., controllers, services, and repositories) are properly integrated.

Here's how we can set up Cucumber with Spring Boot 3 for the Employee Management Service which we developed using Spring Data Jpa.

Dependencies

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-suite</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-java</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-spring</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-junit-platform-engine</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency>

Create Cucumber Feature File

Create a .feature file in src/test/resources/features called employee_management.feature

Feature: Employee Management API Scenario: Create a new employee Given I have the following employee details: | transactionId | name | designation | salary | location | | T123 | Praveen Oruganti | Solution Architect | 80000 | New York | When I create a new employee Then the employee should be created with name "Praveen Oruganti" Scenario: Fetch all employees Given there are employees in the system When I request the list of employees Then I should receive a list of employees Scenario: Fetch employee by name Given there is an employee with the name "Praveen Oruganti" When I search for employee by name "Praveen Oruganti" Then I should receive the employee details for "Praveen Oruganti" Scenario: Fetch employee by location Given there is an employee with location "New York" When I search for employees by location "New York" Then I should receive a list of employees in "New York" Scenario: Fetch employee by ID Given there is an employee with ID 1 When I request the employee with ID 1 Then I should receive the employee details for ID 1 Scenario: Update an employee Given there is an employee with ID 1 When I update the employee's name to "Praveen" Then the employee name should be updated to "Praveen" Scenario: Delete an employee by ID Given there is an employee with ID 1 When I delete the employee with ID 1 Then the employee with ID 1 should be deleted

Create Step Definitions

In the src/test/java/com/praveen/employee/bdd/steps directory, create a class to map the Gherkin steps to Java code.

package com.praveen.employee.bdd.steps; import com.praveen.employee.bo.EmployeeRequest; import com.praveen.employee.entity.Employee; import com.praveen.employee.repository.EmployeeRepository; import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.*; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import java.util.List; import java.util.Map; import java.util.Objects; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @SpringJUnitConfig public class EmployeeManagementSteps { @Autowired private TestRestTemplate restTemplate; private ResponseEntity<String> response; private EmployeeRequest employeeRequest; private Long employeeId = 1L; @Autowired private EmployeeRepository employeeRepository; @BeforeEach public void setUp() { employeeRepository.deleteAll(); employeeRequest = new EmployeeRequest(); } @Given("I have the following employee details:") public void i_have_the_following_employee_details(DataTable dataTable) { List<Map<String, String>> rows = dataTable.asMaps(String.class, String.class); String transactionId = rows.get(0).get("transactionId"); String name = rows.get(0).get("name"); String designation = rows.get(0).get("designation"); Long salary = Long.parseLong(rows.get(0).get("salary")); String location = rows.get(0).get("location"); employeeRequest = new EmployeeRequest(transactionId, name, designation, salary, location); } @When("I create a new employee") public void i_create_a_new_employee() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<EmployeeRequest> entity = new HttpEntity<>(employeeRequest, headers); response = restTemplate.exchange("/api/v1/employees", HttpMethod.POST, entity, String.class); } @Then("the employee should be created with name {string}") public void the_employee_should_be_created_with_name(String name) { assertEquals(HttpStatus.CREATED, response.getStatusCode()); assertTrue(Objects.requireNonNull(response.getBody()).contains(name)); } @Given("there are employees in the system") public void there_are_employees_in_the_system() { Employee employee = new Employee(employeeId, "Praveen Oruganti", "Solution Architect", 80000L, "New York"); employeeRepository.save(employee); } @When("I request the list of employees") public void i_request_the_list_of_employees() { response = restTemplate.exchange("/api/v1/employees", HttpMethod.GET, null, String.class); } @Then("I should receive a list of employees") public void i_should_receive_a_list_of_employees() { assertEquals(HttpStatus.OK, response.getStatusCode()); assertTrue(Objects.requireNonNull(response.getBody()).contains("Praveen Oruganti")); } @Given("there is an employee with the name {string}") public void there_is_an_employee_with_the_name(String name) { if (employeeRequest == null) { employeeRequest = new EmployeeRequest(); } employeeRequest.setName(name); } @When("I search for employee by name {string}") public void i_search_for_employee_by_name(String name) { response = restTemplate.exchange("/api/v1/employees/search?name=" + name, HttpMethod.GET, null, String.class); } @Then("I should receive the employee details for {string}") public void i_should_receive_the_employee_details_for(String name) { assertEquals(HttpStatus.OK, response.getStatusCode()); assertTrue(Objects.requireNonNull(response.getBody()).contains(name)); } @Given("there is an employee with location {string}") public void there_is_an_employee_with_location(String location) { Employee employee = new Employee(employeeId, "Praveen Oruganti", "Solution Architect", 80000L, location); employeeRepository.save(employee); } @When("I search for employees by location {string}") public void i_search_for_employees_by_location(String location) { response = restTemplate.exchange("/api/v1/employees/search/location/" + location, HttpMethod.GET, null, String.class); } @Then("I should receive a list of employees in {string}") public void i_should_receive_a_list_of_employees_in(String location) { assertEquals(HttpStatus.OK, response.getStatusCode()); assertTrue(Objects.requireNonNull(response.getBody()).contains(location)); } @Given("there is an employee with ID {int}") public void there_is_an_employee_with_id(Integer id) { employeeId = Long.valueOf(id); } @When("I request the employee with ID {int}") public void i_request_the_employee_with_id(Integer id) { response = restTemplate.exchange("/api/v1/employees/" + id, HttpMethod.GET, null, String.class); } @Then("I should receive the employee details for ID {int}") public void i_should_receive_the_employee_details_for_id(Integer id) { assertEquals(HttpStatus.OK, response.getStatusCode()); assertTrue(Objects.requireNonNull(response.getBody()).contains(id.toString())); } @When("I update the employee's name to {string}") public void i_update_the_employee_name_to(String newName) { if (employeeRequest == null) { employeeRequest = new EmployeeRequest(); } employeeRequest.setName(newName); employeeRequest.setDesignation("Solution Architect"); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<EmployeeRequest> entity = new HttpEntity<>(employeeRequest, headers); response = restTemplate.exchange("/api/v1/employees/" + employeeId, HttpMethod.PUT, entity, String.class); } @Then("the employee name should be updated to {string}") public void the_employee_name_should_be_updated_to(String newName) { assertEquals(HttpStatus.OK, response.getStatusCode()); assertTrue(Objects.requireNonNull(response.getBody()).contains(newName)); } @When("I delete the employee with ID {int}") public void i_delete_the_employee_with_id(Integer id) { response = restTemplate.exchange("/api/v1/employees/" + id, HttpMethod.DELETE, null, String.class); } @Then("the employee with ID {int} should be deleted") public void the_employee_with_id_should_be_deleted(Integer id) { assertEquals(HttpStatus.OK, response.getStatusCode()); } }

Configure Cucumber in Spring Boot

package com.praveen.employee; import org.junit.platform.suite.api.*; import static io.cucumber.junit.platform.engine.Constants.*; @Suite @IncludeEngines("cucumber") @SelectClasspathResource("features") @ConfigurationParameters({ @ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "html:target/cucumber/cucumber-report.html"), @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.praveen.employee.bdd"), @ConfigurationParameter(key = FILTER_TAGS_PROPERTY_NAME, value = "not @Disabled"), @ConfigurationParameter(key = PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, value = "true"), @ConfigurationParameter(key = JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, value = "long") }) public class DemoFeatures { }
package com.praveen.employee.bdd.tooling; import io.cucumber.spring.CucumberContextConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @CucumberContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc public class SpringGlue { }

Let's execute DemoFeatures, then cucumber-report.html will be generated in target/cucumber folder.

screenshot of the app