Introduction to BDD and Cucumber
Behavior-Driven Development (BDD) is an agile software development process that encourages collaboration between developers, QA, and non-technical participants. Cucumber is a tool that supports BDD by allowing the execution of feature documentation written in plain text.
Setup and Dependencies
Maven Dependencies
<properties> <cucumber.version>7.15.0</cucumber.version> </properties> <dependencies> <!-- Cucumber Java --> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-java</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> <!-- Cucumber JUnit --> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-junit</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> <!-- Cucumber Spring (if using Spring) --> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-spring</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> <!-- AssertJ for fluent assertions --> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.24.2</version> <scope>test</scope> </dependency> <!-- JUnit --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> </plugin> </plugins> </build> Project Structure
src/ ├── main/ │ └── java/ │ └── com/example/ │ ├── model/ │ ├── service/ │ └── controller/ └── test/ ├── java/ │ └── com/example/ │ ├── runner/ │ ├── stepdefs/ │ └── config/ └── resources/ └── features/
Basic Cucumber Implementation
Feature Files
src/test/resources/features/login/login.feature
Feature: User Login As a user I want to login to the application So that I can access my account Background: Given the application is running And the following users exist: | username | password | status | | john | pass123 | ACTIVE | | jane | pass456 | LOCKED | Scenario: Successful login with valid credentials When I navigate to the login page And I enter username "john" and password "pass123" And I click the login button Then I should be redirected to the dashboard And I should see a welcome message "Welcome, john!" Scenario: Failed login with invalid password When I navigate to the login page And I enter username "john" and password "wrongpass" And I click the login button Then I should see an error message "Invalid credentials" And I should remain on the login page Scenario: Failed login with locked account When I navigate to the login page And I enter username "jane" and password "pass456" And I click the login button Then I should see an error message "Account is locked" And I should remain on the login page Scenario Outline: Login with multiple test data When I navigate to the login page And I enter username "<username>" and password "<password>" And I click the login button Then I should see "<expected_message>" Examples: | username | password | expected_message | | john | pass123 | Welcome, john! | | john | wrong | Invalid credentials | | unknown | pass123 | Invalid credentials |
Step Definitions
src/test/java/com/example/stepdefs/LoginStepDefinitions.java
package com.example.stepdefs; import io.cucumber.java.Before; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; import io.cucumber.java.en.Then; import io.cucumber.datatable.DataTable; import org.assertj.core.api.Assertions; import com.example.model.User; import com.example.service.LoginService; import com.example.service.AuthResult; import java.util.List; import java.util.Map; public class LoginStepDefinitions { private LoginService loginService; private AuthResult authResult; private String currentPage; private String errorMessage; @Before public void setUp() { loginService = new LoginService(); authResult = null; currentPage = "login"; errorMessage = null; } @Given("the application is running") public void the_application_is_running() { // Initialize application context System.out.println("Application is running..."); } @Given("the following users exist:") public void the_following_users_exist(DataTable dataTable) { List<Map<String, String>> users = dataTable.asMaps(); for (Map<String, String> user : users) { User newUser = new User( user.get("username"), user.get("password"), user.get("status") ); loginService.addUser(newUser); } } @When("I navigate to the login page") public void i_navigate_to_the_login_page() { currentPage = "login"; System.out.println("Navigated to login page"); } @When("I enter username {string} and password {string}") public void i_enter_username_and_password(String username, String password) { System.out.println("Entering username: " + username + " and password: " + password); // In real implementation, this would set values in the UI } @When("I click the login button") public void i_click_the_login_button() { // Simulate login attempt try { authResult = loginService.authenticate(getCurrentUsername(), getCurrentPassword()); if (authResult.isSuccess()) { currentPage = "dashboard"; } else { errorMessage = authResult.getMessage(); } } catch (Exception e) { errorMessage = e.getMessage(); } } @Then("I should be redirected to the dashboard") public void i_should_be_redirected_to_the_dashboard() { Assertions.assertThat(currentPage) .isEqualTo("dashboard") .withFailMessage("Expected to be on dashboard, but was on: " + currentPage); } @Then("I should see a welcome message {string}") public void i_should_see_a_welcome_message(String expectedMessage) { Assertions.assertThat(authResult.getMessage()) .isEqualTo(expectedMessage); } @Then("I should see an error message {string}") public void i_should_see_an_error_message(String expectedErrorMessage) { Assertions.assertThat(errorMessage) .isEqualTo(expectedErrorMessage); } @Then("I should remain on the login page") public void i_should_remain_on_the_login_page() { Assertions.assertThat(currentPage) .isEqualTo("login"); } // Helper methods to simulate UI state private String getCurrentUsername() { // In real implementation, this would get from UI state return "john"; // Simplified for example } private String getCurrentPassword() { // In real implementation, this would get from UI state return "pass123"; // Simplified for example } } Test Runner
src/test/java/com/example/runner/CucumberTestRunner.java
package com.example.runner; import io.cucumber.junit.Cucumber; import io.cucumber.junit.CucumberOptions; import org.junit.runner.RunWith; @RunWith(Cucumber.class) @CucumberOptions( features = "src/test/resources/features", glue = {"com.example.stepdefs", "com.example.config"}, plugin = { "pretty", "html:target/cucumber-reports/cucumber.html", "json:target/cucumber-reports/cucumber.json", "junit:target/cucumber-reports/cucumber.xml" }, monochrome = true, tags = "@Login and not @WIP" ) public class CucumberTestRunner { } Domain Models and Services
src/main/java/com/example/model/User.java
package com.example.model; public class User { private String username; private String password; private String status; public User(String username, String password, String status) { this.username = username; this.password = password; this.status = status; } // Getters and setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return "User{username='" + username + "', status='" + status + "'}"; } } src/main/java/com/example/service/AuthResult.java
package com.example.service; public class AuthResult { private boolean success; private String message; private User user; public AuthResult(boolean success, String message, User user) { this.success = success; this.message = message; this.user = user; } public AuthResult(boolean success, String message) { this(success, message, null); } // Getters public boolean isSuccess() { return success; } public String getMessage() { return message; } public User getUser() { return user; } // Static factory methods public static AuthResult success(User user) { return new AuthResult(true, "Welcome, " + user.getUsername() + "!", user); } public static AuthResult failure(String message) { return new AuthResult(false, message); } } src/main/java/com/example/service/LoginService.java
package com.example.service; import com.example.model.User; import java.util.HashMap; import java.util.Map; public class LoginService { private Map<String, User> users = new HashMap<>(); public void addUser(User user) { users.put(user.getUsername(), user); } public AuthResult authenticate(String username, String password) { User user = users.get(username); if (user == null) { return AuthResult.failure("Invalid credentials"); } if ("LOCKED".equals(user.getStatus())) { return AuthResult.failure("Account is locked"); } if (!user.getPassword().equals(password)) { return AuthResult.failure("Invalid credentials"); } return AuthResult.success(user); } public User getUser(String username) { return users.get(username); } public void clearUsers() { users.clear(); } } Advanced Cucumber Features
Data Tables and Transformations
src/test/resources/features/registration/user_registration.feature
Feature: User Registration As a new user I want to register for an account So that I can use the application Scenario: Successful user registration Given I am on the registration page When I fill the registration form with: | Field | Value | | First Name | John | | Last Name | Doe | | Email | [email protected] | | Password | SecurePass123! | | Phone | 1234567890 | And I accept the terms and conditions And I submit the registration form Then I should see a success message "Registration successful" And I should receive a confirmation email And the following user should be created in the database: | Field | Value | | First Name | John | | Last Name | Doe | | Email | [email protected] | | Status | PENDING | Scenario: Registration with invalid email Given I am on the registration page When I fill the registration form with: | Field | Value | | First Name | Jane | | Last Name | Smith | | Email | invalid-email | | Password | SecurePass123! | And I submit the registration form Then I should see an error message "Invalid email format" And I should remain on the registration page
src/test/java/com/example/stepdefs/RegistrationStepDefinitions.java
package com.example.stepdefs; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; import io.cucumber.java.en.Then; import io.cucumber.datatable.DataTable; import org.assertj.core.api.Assertions; import com.example.model.RegistrationForm; import com.example.service.RegistrationService; import com.example.model.User; import java.util.Map; public class RegistrationStepDefinitions { private RegistrationService registrationService; private RegistrationForm currentForm; private String currentPage; private String resultMessage; private boolean termsAccepted; @Given("I am on the registration page") public void i_am_on_the_registration_page() { currentPage = "registration"; currentForm = new RegistrationForm(); termsAccepted = false; registrationService = new RegistrationService(); } @When("I fill the registration form with:") public void i_fill_the_registration_form_with(DataTable dataTable) { Map<String, String> formData = dataTable.asMap(); currentForm.setFirstName(formData.get("First Name")); currentForm.setLastName(formData.get("Last Name")); currentForm.setEmail(formData.get("Email")); currentForm.setPassword(formData.get("Password")); currentForm.setPhone(formData.get("Phone")); System.out.println("Filled form: " + currentForm); } @When("I accept the terms and conditions") public void i_accept_the_terms_and_conditions() { termsAccepted = true; } @When("I submit the registration form") public void i_submit_the_registration_form() { if (!termsAccepted) { resultMessage = "Please accept terms and conditions"; return; } try { User registeredUser = registrationService.registerUser(currentForm); resultMessage = "Registration successful"; currentPage = "success"; } catch (IllegalArgumentException e) { resultMessage = e.getMessage(); } } @Then("I should see a success message {string}") public void i_should_see_a_success_message(String expectedMessage) { Assertions.assertThat(resultMessage).isEqualTo(expectedMessage); } @Then("I should see an error message {string}") public void i_should_see_an_error_message(String expectedMessage) { Assertions.assertThat(resultMessage).isEqualTo(expectedMessage); } @Then("I should remain on the registration page") public void i_should_remain_on_the_registration_page() { Assertions.assertThat(currentPage).isEqualTo("registration"); } @Then("I should receive a confirmation email") public void i_should_receive_a_confirmation_email() { // In real implementation, verify email was sent System.out.println("Confirmation email sent to: " + currentForm.getEmail()); } @Then("the following user should be created in the database:") public void the_following_user_should_be_created_in_the_database(DataTable dataTable) { Map<String, String> expectedData = dataTable.asMap(); User user = registrationService.findUserByEmail(expectedData.get("Email")); Assertions.assertThat(user).isNotNull(); Assertions.assertThat(user.getFirstName()).isEqualTo(expectedData.get("First Name")); Assertions.assertThat(user.getLastName()).isEqualTo(expectedData.get("Last Name")); Assertions.assertThat(user.getEmail()).isEqualTo(expectedData.get("Email")); Assertions.assertThat(user.getStatus()).isEqualTo(expectedData.get("Status")); } } Custom Data Types and Transformers
src/test/java/com/example/stepdefs/transforms/UserTransformer.java
package com.example.stepdefs.transforms; import io.cucumber.java.DataTableType; import com.example.model.User; import java.util.Map; public class UserTransformer { @DataTableType public User userEntry(Map<String, String> entry) { return new User( entry.get("username"), entry.get("password"), entry.get("status") ); } @DataTableType public RegistrationForm registrationFormEntry(Map<String, String> entry) { RegistrationForm form = new RegistrationForm(); form.setFirstName(entry.get("First Name")); form.setLastName(entry.get("Last Name")); form.setEmail(entry.get("Email")); form.setPassword(entry.get("Password")); form.setPhone(entry.get("Phone")); return form; } } Hooks and Background Setup
src/test/java/com/example/stepdefs/Hooks.java
package com.example.stepdefs; import io.cucumber.java.After; import io.cucumber.java.Before; import io.cucumber.java.Scenario; import com.example.service.DatabaseService; import com.example.service.EmailService; public class Hooks { private DatabaseService databaseService; private EmailService emailService; @Before(order = 10) public void setUp(Scenario scenario) { System.out.println("Starting scenario: " + scenario.getName()); System.out.println("Tags: " + scenario.getSourceTagNames()); databaseService = new DatabaseService(); emailService = new EmailService(); // Clear previous test data databaseService.clearTestData(); emailService.clearSentEmails(); } @Before(value = "@Database", order = 20) public void setupDatabase() { System.out.println("Setting up database for database-related tests"); databaseService.initializeTestSchema(); } @Before(value = "@Email", order = 20) public void setupEmailService() { System.out.println("Setting up email service for email-related tests"); emailService.startTestMode(); } @After(order = 10) public void tearDown(Scenario scenario) { if (scenario.isFailed()) { System.out.println("Scenario failed: " + scenario.getName()); // Take screenshot or log additional info takeScreenshot(scenario); } System.out.println("Finished scenario: " + scenario.getName()); } @After(value = "@Cleanup", order = 5) public void cleanupTestData() { System.out.println("Performing additional cleanup"); databaseService.cleanup(); emailService.cleanup(); } private void takeScreenshot(Scenario scenario) { // In real implementation, take screenshot for UI tests System.out.println("Taking screenshot for failed scenario: " + scenario.getName()); // scenario.embed(screenshotBytes, "image/png", scenario.getName()); } } Spring Integration
Spring Configuration for Cucumber
src/test/java/com/example/config/CucumberSpringConfiguration.java
package com.example.config; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; import io.cucumber.spring.CucumberContextConfiguration; @CucumberContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ContextConfiguration public class CucumberSpringConfiguration { // Spring configuration will be automatically loaded } Spring-Based Step Definitions
src/test/java/com/example/stepdefs/SpringLoginStepDefinitions.java
package com.example.stepdefs; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; import io.cucumber.java.en.Then; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.ResponseEntity; import org.assertj.core.api.Assertions; import com.example.model.User; import com.example.repository.UserRepository; public class SpringLoginStepDefinitions { @Autowired private TestRestTemplate restTemplate; @Autowired private UserRepository userRepository; private ResponseEntity<String> response; private String authToken; @Given("a user exists with username {string} and password {string}") public void a_user_exists_with_username_and_password(String username, String password) { User user = new User(username, password, "ACTIVE"); userRepository.save(user); } @When("I send a login request with username {string} and password {string}") public void i_send_a_login_request_with_username_and_password(String username, String password) { String loginUrl = "/api/auth/login"; LoginRequest loginRequest = new LoginRequest(username, password); response = restTemplate.postForEntity(loginUrl, loginRequest, String.class); if (response.getStatusCode().is2xxSuccessful()) { // Extract token from response authToken = response.getBody(); } } @Then("the response status should be {int}") public void the_response_status_should_be(Integer expectedStatus) { Assertions.assertThat(response.getStatusCodeValue()) .isEqualTo(expectedStatus); } @Then("I should receive an authentication token") public void i_should_receive_an_authentication_token() { Assertions.assertThat(authToken).isNotNull(); Assertions.assertThat(authToken).isNotEmpty(); } @Then("the response should contain message {string}") public void the_response_should_contain_message(String expectedMessage) { Assertions.assertThat(response.getBody()).contains(expectedMessage); } // DTO for login request public static class LoginRequest { private String username; private String password; public LoginRequest(String username, String password) { this.username = username; this.password = password; } // Getters and setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } } REST API Testing
src/test/resources/features/api/user_management.feature
Feature: User Management API As an API client I want to manage users through REST endpoints So that I can integrate with the user management system Background: Given the API is available And I have a valid admin token @API @UserManagement Scenario: Create a new user via API When I send a POST request to "/api/users" with: """ { "username": "testuser", "email": "[email protected]", "firstName": "Test", "lastName": "User", "password": "SecurePass123!" } """ Then the response status should be 201 And the response should contain: """ { "id": 1, "username": "testuser", "email": "[email protected]", "status": "ACTIVE" } """ And the user should be persisted in the database @API @UserManagement Scenario: Get user details via API Given a user exists with id "1" When I send a GET request to "/api/users/1" Then the response status should be 200 And the response should contain: """ { "id": 1, "username": "testuser", "email": "[email protected]" } """ @API @UserManagement Scenario: Update user via API Given a user exists with id "1" When I send a PUT request to "/api/users/1" with: """ { "firstName": "Updated", "lastName": "Name" } """ Then the response status should be 200 And the response should contain: """ { "firstName": "Updated", "lastName": "Name" } """ @API @UserManagement Scenario: Delete user via API Given a user exists with id "1" When I send a DELETE request to "/api/users/1" Then the response status should be 204 And the user should be removed from the database src/test/java/com/example/stepdefs/ApiStepDefinitions.java
package com.example.stepdefs; import io.cucumber.java.en.Given; import io.cucumber.java.en.When; import io.cucumber.java.en.Then; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.*; import org.assertj.core.api.Assertions; import com.example.model.User; import com.example.repository.UserRepository; import com.fasterxml.jackson.databind.ObjectMapper; public class ApiStepDefinitions { @Autowired private TestRestTemplate restTemplate; @Autowired private UserRepository userRepository; @Autowired private ObjectMapper objectMapper; private ResponseEntity<String> response; private String authToken; private HttpHeaders headers; @Given("the API is available") public void the_api_is_available() { // Spring Boot test will ensure application is running System.out.println("API is available"); } @Given("I have a valid admin token") public void i_have_a_valid_admin_token() { // Obtain authentication token authToken = "admin-token-123"; // In real implementation, get from auth service headers = new HttpHeaders(); headers.setBearerAuth(authToken); headers.setContentType(MediaType.APPLICATION_JSON); } @Given("a user exists with id {string}") public void a_user_exists_with_id(String userId) { User user = new User("testuser", "password", "ACTIVE"); user.setId(Long.parseLong(userId)); userRepository.save(user); } @When("I send a {string} request to {string} with:") public void i_send_a_request_to_with(String method, String endpoint, String body) { HttpMethod httpMethod = HttpMethod.valueOf(method.toUpperCase()); HttpEntity<String> request = new HttpEntity<>(body, headers); response = restTemplate.exchange(endpoint, httpMethod, request, String.class); } @Then("the response status should be {int}") public void the_response_status_should_be(Integer expectedStatus) { Assertions.assertThat(response.getStatusCodeValue()) .isEqualTo(expectedStatus); } @Then("the response should contain:") public void the_response_should_contain(String expectedJson) { try { // Parse both JSON strings to compare structure, ignoring extra fields var expectedNode = objectMapper.readTree(expectedJson); var actualNode = objectMapper.readTree(response.getBody()); // Custom assertion to check if expected fields exist in actual response assertJsonContains(expectedNode, actualNode); } catch (Exception e) { throw new RuntimeException("Failed to parse JSON", e); } } @Then("the user should be persisted in the database") public void the_user_should_be_persisted_in_the_database() { User user = userRepository.findByUsername("testuser"); Assertions.assertThat(user).isNotNull(); Assertions.assertThat(user.getEmail()).isEqualTo("[email protected]"); } @Then("the user should be removed from the database") public void the_user_should_be_removed_from_the_database() { User user = userRepository.findByUsername("testuser"); Assertions.assertThat(user).isNull(); } private void assertJsonContains(com.fasterxml.jackson.databind.JsonNode expected, com.fasterxml.jackson.databind.JsonNode actual) { expected.fields().forEachRemaining(entry -> { String field = entry.getKey(); var expectedValue = entry.getValue(); var actualValue = actual.get(field); Assertions.assertThat(actualValue) .withFailMessage("Field '%s' not found in response", field) .isNotNull(); Assertions.assertThat(actualValue.toString()) .withFailMessage("Field '%s' value mismatch. Expected: %s, Actual: %s", field, expectedValue, actualValue) .isEqualTo(expectedValue.toString()); }); } } Parallel Execution and Reporting
JUnit 5 Parallel Runner
src/test/java/com/example/runner/ParallelCucumberTest.java
package com.example.runner; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.SelectClasspathResource; import org.junit.platform.suite.api.Suite; import static io.cucumber.junit.platform.engine.Constants.*; @Suite @IncludeEngines("cucumber") @SelectClasspathResource("features") @ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty, html:target/cucumber-reports/cucumber.html, json:target/cucumber-reports/cucumber.json") @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example.stepdefs") @ConfigurationParameter(key = FILTER_TAGS_PROPERTY_NAME, value = "@Test and not @WIP") @ConfigurationParameter(key = PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, value = "true") @ConfigurationParameter(key = PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, value = "fixed") @ConfigurationParameter(key = PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, value = "4") @ConfigurationParameter(key = PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME, value = "8") public class ParallelCucumberTest { } Configuration for Parallel Execution
src/test/resources/junit-platform.properties
cucumber.execution.parallel.enabled=true cucumber.execution.parallel.config.strategy=fixed cucumber.execution.parallel.config.fixed.parallelism=4 cucumber.execution.parallel.config.fixed.max-pool-size=8 cucumber.plugin=pretty, html:target/cucumber-reports/cucumber.html, json:target/cucumber-reports/cucumber.json, junit:target/cucumber-reports/cucumber.xml cucumber.filter.tags=@Test and not @WIP
Best Practices and Patterns
1. Page Object Pattern for UI Tests
src/test/java/com/example/pages/LoginPage.java
package com.example.pages; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; public class LoginPage { private WebDriver driver; @FindBy(id = "username") private WebElement usernameField; @FindBy(id = "password") private WebElement passwordField; @FindBy(id = "login-button") private WebElement loginButton; @FindBy(className = "error-message") private WebElement errorMessage; @FindBy(className = "welcome-message") private WebElement welcomeMessage; public LoginPage(WebDriver driver) { this.driver = driver; PageFactory.initElements(driver, this); } public void navigateTo() { driver.get("http://localhost:8080/login"); } public void enterUsername(String username) { usernameField.clear(); usernameField.sendKeys(username); } public void enterPassword(String password) { passwordField.clear(); passwordField.sendKeys(password); } public void clickLogin() { loginButton.click(); } public String getErrorMessage() { return errorMessage.getText(); } public String getWelcomeMessage() { return welcomeMessage.getText(); } public boolean isOnLoginPage() { return driver.getCurrentUrl().contains("/login"); } public boolean isOnDashboard() { return driver.getCurrentUrl().contains("/dashboard"); } } 2. Configuration Management
src/test/java/com/example/config/TestConfig.java
package com.example.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @ConfigurationProperties(prefix = "test") public class TestConfig { private String baseUrl; private int timeout; private Browser browser; public enum Browser { CHROME, FIREFOX, EDGE } // Getters and setters public String getBaseUrl() { return baseUrl; } public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } public Browser getBrowser() { return browser; } public void setBrowser(Browser browser) { this.browser = browser; } @Bean public TestConfig testConfig() { return this; } } 3. Shared Test Context
src/test/java/com/example/stepdefs/TestContext.java
package com.example.stepdefs; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.Map; @Component public class TestContext { private Map<String, Object> context = new HashMap<>(); private RestTemplate restTemplate = new RestTemplate(); public void set(String key, Object value) { context.put(key, value); } @SuppressWarnings("unchecked") public <T> T get(String key) { return (T) context.get(key); } public <T> T get(String key, Class<T> type) { return type.cast(context.get(key)); } public void clear() { context.clear(); } public RestTemplate getRestTemplate() { return restTemplate; } } Running Tests
Command Line Execution
# Run all tests mvn test # Run specific feature mvn test -Dcucumber.options="src/test/resources/features/login" # Run with specific tags mvn test -Dcucumber.options="--tags @Login" # Run with specific tags excluding others mvn test -Dcucumber.options="--tags @Test --tags ~@WIP" # Generate reports mvn test -Dcucumber.options="--plugin html:target/cucumber-reports"
Common Cucumber Options
@CucumberOptions( features = "src/test/resources/features", glue = "com.example.stepdefs", plugin = { "pretty", // Console output "html:target/cucumber-reports/cucumber.html", // HTML report "json:target/cucumber-reports/cucumber.json", // JSON report "junit:target/cucumber-reports/cucumber.xml", // JUnit report "rerun:target/rerun.txt" // Rerun failed tests }, monochrome = true, // Clean console output tags = "@Regression and not @Slow", // Tag filtering dryRun = false, // Check step definitions without running strict = true, // Fail if undefined steps snippets = SnippetType.CAMELCASE // Step definition naming ) This comprehensive BDD testing setup with Cucumber in Java provides a robust framework for writing executable specifications that bridge the gap between business requirements and technical implementation.