
Cucumber BDD Guide: Behavior-Driven Development for Testing
Behavior-Driven Development (BDD) bridges the gap between business stakeholders and technical teams by using a shared language to describe software behavior. Cucumber implements BDD by letting you write tests in plain English (or other languages) that anyone can understand, then automating those tests with code.
This guide covers Cucumber from basic concepts to advanced patterns, with examples in Java, JavaScript, and Python.
Table Of Contents-
What is BDD and Cucumber?
Behavior-Driven Development (BDD) is a development approach that:
- Focuses on business value and user behavior
- Uses a shared language between stakeholders and developers
- Creates executable specifications that serve as documentation
- Encourages collaboration before implementation
Cucumber is a tool that:
- Parses Gherkin feature files (plain text specifications)
- Executes code (step definitions) linked to each step
- Reports pass/fail status in business terms
- Supports multiple programming languages
BDD Workflow
┌─────────────────────────────────────────────────────────────┐
│ 1. Discovery: Discuss behavior with stakeholders │
│ "What should happen when a user tries to login?" │
└──────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. Formulation: Write scenarios in Gherkin │
│ Given a registered user │
│ When they enter valid credentials │
│ Then they should see the dashboard │
└──────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. Automation: Implement step definitions │
│ @When("they enter valid credentials") │
│ public void enterCredentials() { ... } │
└──────────────────────────────────────────────────────────────┘Gherkin Syntax
Gherkin is the language for writing Cucumber specifications.
Basic Structure
# features/login.feature
Feature: User Login
As a registered user
I want to log into my account
So that I can access my personalized dashboard
Background:
Given the login page is displayed
Scenario: Successful login with valid credentials
Given a user "john@example.com" with password "secret123"
When the user enters their credentials
And clicks the login button
Then the user should be redirected to the dashboard
And a welcome message should be displayed
Scenario: Failed login with invalid password
Given a user "john@example.com" with password "wrongpassword"
When the user enters their credentials
And clicks the login button
Then an error message "Invalid credentials" should be displayed
And the user should remain on the login pageKeywords
| Keyword | Purpose |
|---|---|
| Feature | Describes the feature being tested |
| Scenario | A single test case with steps |
| Given | Preconditions, setup |
| When | Action being tested |
| Then | Expected outcome |
| And/But | Additional steps (same type as previous) |
| Background | Steps run before each scenario |
| Scenario Outline | Template for parameterized tests |
| Examples | Data for scenario outlines |
Gherkin keywords have translations for many languages. You can write scenarios in French, Spanish, Japanese, and dozens of other languages.
Setting Up Cucumber
Java Setup (Maven)
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.15.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>7.15.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>JavaScript Setup (npm)
npm install --save-dev @cucumber/cucumber// cucumber.js
module.exports = {
default: {
require: ['features/step_definitions/**/*.js'],
format: ['progress', 'html:reports/cucumber-report.html'],
},
};Python Setup
pip install behaveProject Structure
project/
├── src/main/java/ # Application code
├── src/test/
│ ├── java/
│ │ └── com/example/
│ │ └── steps/
│ │ └── LoginSteps.java
│ └── resources/
│ └── features/
│ └── login.feature
└── pom.xmlStep Definitions
Step definitions link Gherkin steps to code.
Java Step Definitions
// src/test/java/com/example/steps/LoginSteps.java
package com.example.steps;
import io.cucumber.java.en.*;
import static org.junit.jupiter.api.Assertions.*;
public class LoginSteps {
private String currentUser;
private String currentPassword;
private String loginResult;
@Given("the login page is displayed")
public void loginPageIsDisplayed() {
// Navigate to login page
System.out.println("Navigating to login page");
}
@Given("a user {string} with password {string}")
public void aUserWithPassword(String email, String password) {
this.currentUser = email;
this.currentPassword = password;
}
@When("the user enters their credentials")
public void userEntersCredentials() {
// Fill in form fields
System.out.println("Entering: " + currentUser);
}
@When("clicks the login button")
public void clicksLoginButton() {
// Click submit
loginResult = authenticate(currentUser, currentPassword);
}
@Then("the user should be redirected to the dashboard")
public void shouldRedirectToDashboard() {
assertEquals("success", loginResult);
}
@Then("an error message {string} should be displayed")
public void errorMessageDisplayed(String expectedMessage) {
assertEquals(expectedMessage, loginResult);
}
private String authenticate(String user, String pass) {
// Simulate authentication
return pass.equals("secret123") ? "success" : "Invalid credentials";
}
}JavaScript Step Definitions
// features/step_definitions/loginSteps.js
const { Given, When, Then } = require('@cucumber/cucumber');
const { expect } = require('chai');
let currentUser, currentPassword, loginResult;
Given('the login page is displayed', function () {
// Navigate to login page
});
Given('a user {string} with password {string}', function (email, password) {
currentUser = email;
currentPassword = password;
});
When('the user enters their credentials', function () {
// Fill form
});
When('clicks the login button', function () {
loginResult = authenticate(currentUser, currentPassword);
});
Then('the user should be redirected to the dashboard', function () {
expect(loginResult).to.equal('success');
});
Then('an error message {string} should be displayed', function (message) {
expect(loginResult).to.equal(message);
});
function authenticate(user, pass) {
return pass === 'secret123' ? 'success' : 'Invalid credentials';
}Parameter Types
Given a user "john@example.com" with password "secret"
Given the product costs $29.99
Given the quantity is 5
Given the feature is enabled// String parameters
@Given("a user {string} with password {string}")
public void userWithPassword(String email, String password) { }
// Numeric parameters
@Given("the product costs ${double}")
public void productCosts(Double price) { }
@Given("the quantity is {int}")
public void quantity(Integer qty) { }
// Boolean (custom)
@Given("the feature is enabled")
public void featureEnabled() { }Scenario Outlines
Scenario Outlines run the same scenario with different data.
Feature: Login Validation
Scenario Outline: Login with various credentials
Given a user with email "<email>" and password "<password>"
When the user attempts to login
Then the result should be "<result>"
Examples:
| email | password | result |
| valid@example.com | correct123 | success |
| valid@example.com | wrong | Invalid credentials |
| invalid | any | Invalid email |
| | password | Email required |
Scenario Outline: Password strength validation
Given a user enters password "<password>"
Then password strength should be "<strength>"
Examples: Weak passwords
| password | strength |
| 123456 | weak |
| password | weak |
Examples: Strong passwords
| password | strength |
| Str0ng!Pass | strong |
| C0mplex#2024 | strong |@Given("a user with email {string} and password {string}")
public void userWithCredentials(String email, String password) {
this.email = email;
this.password = password;
}
@When("the user attempts to login")
public void attemptLogin() {
result = loginService.authenticate(email, password);
}
@Then("the result should be {string}")
public void verifyResult(String expectedResult) {
assertEquals(expectedResult, result);
}Hooks and Tags
Hooks
Hooks run before/after scenarios or steps:
import io.cucumber.java.*;
public class Hooks {
@Before
public void beforeScenario(Scenario scenario) {
System.out.println("Starting: " + scenario.getName());
}
@After
public void afterScenario(Scenario scenario) {
if (scenario.isFailed()) {
// Take screenshot, log details
}
System.out.println("Finished: " + scenario.getName());
}
@BeforeStep
public void beforeStep() {
// Runs before each step
}
@AfterStep
public void afterStep() {
// Runs after each step
}
}Tags
Tags organize and filter scenarios:
@smoke @login
Feature: User Login
@happy-path
Scenario: Successful login
Given valid credentials
When user logs in
Then dashboard is displayed
@negative @security
Scenario: Login with SQL injection attempt
Given malicious input
When user attempts login
Then request should be rejectedRunning tagged scenarios:
# Run only smoke tests
cucumber --tags "@smoke"
# Run smoke OR login
cucumber --tags "@smoke or @login"
# Run smoke AND login
cucumber --tags "@smoke and @login"
# Exclude slow tests
cucumber --tags "not @slow"Tagged hooks:
@Before("@database")
public void setupDatabase() {
// Only runs for scenarios tagged @database
}
@After("@browser")
public void closeBrowser() {
// Only runs for scenarios tagged @browser
}Data Tables
Data tables pass structured data to steps:
Scenario: Create multiple users
Given the following users exist:
| email | name | role |
| admin@example.com | Admin | admin |
| user@example.com | User | member |
| guest@example.com | Guest | guest |
When I view the user list
Then I should see 3 usersimport io.cucumber.datatable.DataTable;
import java.util.List;
import java.util.Map;
@Given("the following users exist:")
public void usersExist(DataTable dataTable) {
// As list of maps
List<Map<String, String>> users = dataTable.asMaps();
for (Map<String, String> user : users) {
String email = user.get("email");
String name = user.get("name");
String role = user.get("role");
userService.create(email, name, role);
}
}
// Alternative: Use a custom type
@Given("the following users exist:")
public void usersExist(List<User> users) {
users.forEach(userService::create);
}Vertical Tables
Scenario: User profile
Given a user with details:
| name | John Doe |
| email | john@example.com |
| phone | 555-1234 |@Given("a user with details:")
public void userWithDetails(Map<String, String> details) {
String name = details.get("name");
String email = details.get("email");
String phone = details.get("phone");
}Sharing State
Dependency Injection (Java)
// Use PicoContainer (built-in) or other DI frameworks
public class ScenarioContext {
private String currentUser;
private WebDriver driver;
// Getters and setters
}
public class LoginSteps {
private final ScenarioContext context;
public LoginSteps(ScenarioContext context) {
this.context = context;
}
@When("the user logs in as {string}")
public void loginAs(String user) {
context.setCurrentUser(user);
}
}
public class DashboardSteps {
private final ScenarioContext context;
public DashboardSteps(ScenarioContext context) {
this.context = context;
}
@Then("the dashboard shows user {string}")
public void dashboardShowsUser(String expectedUser) {
assertEquals(expectedUser, context.getCurrentUser());
}
}World Object (JavaScript)
// features/support/world.js
const { setWorldConstructor } = require('@cucumber/cucumber');
class CustomWorld {
constructor() {
this.currentUser = null;
this.response = null;
}
async login(user, password) {
this.response = await api.login(user, password);
this.currentUser = user;
}
}
setWorldConstructor(CustomWorld);
// In steps
Given('user {string} is logged in', async function (user) {
await this.login(user, 'password');
});Best Practices
Write Scenarios from User Perspective
# Good - user-focused
Scenario: User purchases a product
Given I have added a laptop to my cart
When I complete the checkout process
Then I should receive an order confirmation email
# Avoid - implementation-focused
Scenario: Test checkout API
Given POST /cart with productId=123
When POST /checkout with paymentMethod=credit
Then response status is 200Keep Steps Declarative
# Good - declarative
Given a logged-in user with items in cart
# Avoid - imperative
Given I navigate to login page
And I enter "user@test.com" in email field
And I enter "password" in password field
And I click login button
And I wait for dashboard
And I click cart icon
And I add first itemUse Background for Common Setup
Feature: Shopping Cart
Background:
Given a logged-in customer
And the product catalog is loaded
Scenario: Add item to cart
When I add "Laptop" to my cart
Then my cart should contain 1 item
Scenario: Remove item from cart
Given my cart contains a "Laptop"
When I remove "Laptop" from my cart
Then my cart should be emptyReuse Step Definitions
// Create reusable, parameterized steps
@Given("I am logged in as {string}")
@Given("{string} is logged in")
@Given("a logged-in {string}")
public void loggedInAs(String userType) {
User user = testUsers.get(userType);
loginPage.login(user.email, user.password);
}Organize Features by Capability
features/
├── authentication/
│ ├── login.feature
│ ├── logout.feature
│ └── password_reset.feature
├── shopping/
│ ├── cart.feature
│ ├── checkout.feature
│ └── wishlist.feature
└── account/
├── profile.feature
└── preferences.featureCucumber and BDD transform testing from a technical activity into a collaborative process that ensures software meets business needs. By writing tests in plain language that everyone can understand, you create living documentation that stays in sync with your application's actual behavior.
Quiz on Cucumber BDD
Your Score: 0/10
Question: What is the primary purpose of BDD (Behavior-Driven Development)?
Continue Reading
Frequently Asked Questions (FAQs) / People Also Ask (PAA)
Is Cucumber only for acceptance testing?
What's the difference between Cucumber and Behave?
Should step definitions contain assertions?
How do I share state between steps?
Can I use Cucumber with Selenium?
How granular should scenarios be?
What's the difference between Examples and Data Tables?
How do I generate reports from Cucumber runs?