Cucumber-JVM Java library providing annotation-based step definitions for Behavior-Driven Development (BDD) testing
—
Robust data table and doc string handling with custom transformations and built-in collection support. Cucumber-Java provides comprehensive support for handling complex test data through data tables and doc strings with automatic conversion to Java collections and custom objects.
Access Gherkin data tables as various Java collection types with automatic type conversion and custom transformations.
/**
* Raw DataTable access for maximum flexibility
*/
public void stepWithDataTable(DataTable table) {
List<List<String>> rows = table.asLists();
List<Map<String, String>> maps = table.asMaps();
}
/**
* List of lists - each row as a list of strings
*/
public void stepWithListOfLists(List<List<String>> table) { }
/**
* List of maps - each row as a map with headers as keys
*/
public void stepWithListOfMaps(List<Map<String, String>> table) { }
/**
* Single map - first column as keys, second column as values
*/
public void stepWithMap(Map<String, String> table) { }
/**
* Map of lists - first column as keys, remaining columns as list values
*/
public void stepWithMapOfLists(Map<String, List<String>> table) { }
/**
* Map of maps - first column as keys, headers and values as nested maps
*/
public void stepWithMapOfMaps(Map<String, Map<String, String>> table) { }Usage Examples:
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Given;
public class DataTableExamples {
// Raw DataTable for custom processing
@Given("the following data:")
public void raw_data_table(DataTable table) {
List<List<String>> rows = table.asLists();
// Process headers
List<String> headers = rows.get(0);
// Process data rows
for (int i = 1; i < rows.size(); i++) {
List<String> row = rows.get(i);
// Process each row
}
// Alternative: get as maps
List<Map<String, String>> maps = table.asMaps(String.class, String.class);
for (Map<String, String> rowMap : maps) {
String name = rowMap.get("name");
String email = rowMap.get("email");
}
}
// List of lists - simple table structure
@Given("the matrix:")
public void matrix_data(List<List<String>> matrix) {
for (List<String> row : matrix) {
for (String cell : row) {
// Process each cell
}
}
}
// List of maps - table with headers
@Given("the following users exist:")
public void users_exist(List<Map<String, String>> users) {
for (Map<String, String> user : users) {
String name = user.get("name");
String email = user.get("email");
int age = Integer.parseInt(user.get("age"));
// Create user object
createUser(name, email, age);
}
}
// Single map - key-value pairs
@Given("the configuration:")
public void configuration(Map<String, String> config) {
String environment = config.get("environment");
String database = config.get("database");
String timeout = config.get("timeout");
// Apply configuration
applyConfig(environment, database, timeout);
}
// Map of lists - grouped data
@Given("the grouped data:")
public void grouped_data(Map<String, List<String>> groups) {
List<String> adminUsers = groups.get("admins");
List<String> regularUsers = groups.get("users");
List<String> guests = groups.get("guests");
// Process each group
}
// Map of maps - hierarchical data
@Given("the user profiles:")
public void user_profiles(Map<String, Map<String, String>> profiles) {
Map<String, String> johnProfile = profiles.get("john");
String johnEmail = johnProfile.get("email");
String johnRole = johnProfile.get("role");
Map<String, String> janeProfile = profiles.get("jane");
String janeEmail = janeProfile.get("email");
String janeRole = janeProfile.get("role");
}
}Automatic conversion of data tables to strongly-typed collections using built-in and custom type converters.
/**
* Built-in numeric type conversions
*/
public void stepWithIntegers(List<List<Integer>> intTable) { }
public void stepWithDoubles(List<List<Double>> doubleTable) { }
public void stepWithFloats(List<List<Float>> floatTable) { }
public void stepWithLongs(List<List<Long>> longTable) { }
public void stepWithBigDecimals(List<List<BigDecimal>> decimalTable) { }
public void stepWithBigIntegers(List<List<BigInteger>> bigIntTable) { }
/**
* Custom object conversion using @DataTableType
*/
public void stepWithCustomObjects(List<CustomObject> objects) { }Usage Examples:
import java.math.BigDecimal;
import java.math.BigInteger;
public class TypedDataTableExamples {
// Integer data table
@Given("the following numbers:")
public void numbers(List<List<Integer>> numberTable) {
for (List<Integer> row : numberTable) {
for (Integer number : row) {
// Process integer values directly
processNumber(number);
}
}
}
// Double precision data
@Given("the coordinates:")
public void coordinates(List<Map<String, Double>> coords) {
for (Map<String, Double> coord : coords) {
Double x = coord.get("x");
Double y = coord.get("y");
Double z = coord.get("z");
// Work with double values directly
plotPoint(x, y, z);
}
}
// Financial data with BigDecimal
@Given("the following prices:")
public void prices(List<Map<String, BigDecimal>> priceData) {
for (Map<String, BigDecimal> item : priceData) {
String product = item.get("product").toString();
BigDecimal price = item.get("price");
BigDecimal tax = item.get("tax");
// Precise decimal calculations
BigDecimal total = price.add(tax);
}
}
// Custom object conversion (requires @DataTableType)
@Given("the following products:")
public void products(List<Product> products) {
for (Product product : products) {
// Work with strongly-typed Product objects
saveProduct(product);
}
}
// Mixed type map
@Given("the test data:")
public void test_data(Map<String, Object> data) {
// Note: requires @DefaultDataTableCellTransformer for Object conversion
String name = (String) data.get("name");
Integer count = (Integer) data.get("count");
Boolean active = (Boolean) data.get("active");
}
}Access Gherkin doc strings as raw strings or convert to custom objects using doc string type transformers.
/**
* Raw doc string access
*/
public void stepWithDocString(String docString) { }
/**
* DocString object with metadata
*/
public void stepWithDocStringObject(DocString docString) {
String content = docString.getContent();
String contentType = docString.getContentType();
}
/**
* Custom object conversion using @DocStringType
*/
public void stepWithCustomDocString(CustomObject parsedDocString) { }Usage Examples:
import io.cucumber.docstring.DocString;
import io.cucumber.java.en.Given;
public class DocStringExamples {
// Raw string doc string
@Given("the following JSON:")
public void json_data(String jsonString) {
// Parse JSON manually
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode json = mapper.readTree(jsonString);
// Process JSON
} catch (Exception e) {
throw new RuntimeException("Invalid JSON", e);
}
}
// DocString object with metadata
@Given("the following document:")
public void document_data(DocString docString) {
String content = docString.getContent();
String contentType = docString.getContentType(); // From """ content_type
// Process based on content type
if ("json".equals(contentType)) {
processJsonContent(content);
} else if ("xml".equals(contentType)) {
processXmlContent(content);
} else {
processPlainText(content);
}
}
// Multi-line text processing
@Given("the following email template:")
public void email_template(String template) {
// Process multi-line email template
String[] lines = template.split("\n");
String subject = extractSubject(lines);
String body = extractBody(lines);
sendEmail(subject, body);
}
// Large text data
@Given("the following log entries:")
public void log_entries(String logData) {
String[] entries = logData.split("\n");
for (String entry : entries) {
if (!entry.trim().isEmpty()) {
parseLogEntry(entry);
}
}
}
// Custom parsed doc string (requires @DocStringType)
@Given("the following configuration:")
public void configuration(Properties config) {
// Automatically parsed from doc string to Properties object
String environment = config.getProperty("environment");
String database = config.getProperty("database.url");
applyConfiguration(config);
}
// Structured data from doc string
@Given("the API response:")
public void api_response(JsonNode response) {
// Automatically parsed from JSON doc string
String status = response.get("status").asText();
JsonNode data = response.get("data");
validateResponse(status, data);
}
}Complex data table operations including transposition, custom transformations, and nested structures.
/**
* Transposed data tables
*/
public void stepWithTransposedTable(@Transpose DataTable table) { }
public void stepWithTransposedObjects(@Transpose List<CustomObject> objects) { }
/**
* Nested data structures
*/
public void stepWithNestedMaps(Map<String, Map<String, Object>> nestedData) { }
public void stepWithNestedLists(List<List<List<String>>> nestedLists) { }
/**
* Custom replacement handling
*/
public void stepWithReplacements(List<CustomObject> objects) { }
// Uses @DataTableType(replaceWithEmptyString = {"[blank]", "null"})Usage Examples:
import io.cucumber.java.Transpose;
import io.cucumber.datatable.DataTable;
public class AdvancedDataTableExamples {
// Transposed table for vertical data layout
@Given("the user profile:")
public void user_profile(@Transpose DataTable profile) {
// Input table: | name | John Doe |
// | email | john@test.com |
// | age | 30 |
//
// After transpose: | name | email | age |
// | John Doe | john@test.com | 30 |
List<Map<String, String>> profileMaps = profile.asMaps();
Map<String, String> userInfo = profileMaps.get(0);
String name = userInfo.get("name");
String email = userInfo.get("email");
int age = Integer.parseInt(userInfo.get("age"));
}
// Complex nested structure
@Given("the department structure:")
public void department_structure(Map<String, Map<String, List<String>>> deptStructure) {
// Table with department -> role -> list of people
Map<String, List<String>> engineering = deptStructure.get("Engineering");
List<String> developers = engineering.get("Developer");
List<String> managers = engineering.get("Manager");
assignPeopleToDepartment("Engineering", "Developer", developers);
assignPeopleToDepartment("Engineering", "Manager", managers);
}
// Custom object with empty value handling
@Given("the product catalog:")
public void product_catalog(List<Product> products) {
// Uses @DataTableType with replaceWithEmptyString configuration
for (Product product : products) {
// Empty/null values automatically handled by transformer
if (product.getDescription() != null) {
processDescription(product.getDescription());
}
}
}
// Multi-dimensional data
@Given("the test matrix:")
public void test_matrix(List<List<TestCase>> testMatrix) {
for (int i = 0; i < testMatrix.size(); i++) {
List<TestCase> row = testMatrix.get(i);
for (int j = 0; j < row.size(); j++) {
TestCase testCase = row.get(j);
runTest(i, j, testCase);
}
}
}
// Dynamic column handling
@Given("the dynamic data:")
public void dynamic_data(DataTable table) {
List<String> headers = table.row(0);
for (int i = 1; i < table.height(); i++) {
Map<String, String> rowData = new HashMap<>();
List<String> row = table.row(i);
for (int j = 0; j < headers.size(); j++) {
String header = headers.get(j);
String value = j < row.size() ? row.get(j) : "";
rowData.put(header, value);
}
processDynamicRow(rowData);
}
}
}Robust error handling and validation for data table and doc string processing.
public class DataValidationExamples {
@Given("the validated user data:")
public void validated_users(List<Map<String, String>> users) {
for (Map<String, String> user : users) {
// Validate required fields
validateRequired(user, "name", "email");
// Validate data formats
String email = user.get("email");
if (!isValidEmail(email)) {
throw new IllegalArgumentException("Invalid email: " + email);
}
// Validate numeric fields
String ageStr = user.get("age");
if (ageStr != null && !ageStr.isEmpty()) {
try {
int age = Integer.parseInt(ageStr);
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Age must be a number: " + ageStr);
}
}
}
}
@Given("the JSON configuration:")
public void json_config(String jsonString) {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode config = mapper.readTree(jsonString);
// Validate required configuration fields
if (!config.has("environment")) {
throw new IllegalArgumentException("Missing required field: environment");
}
// Process validated configuration
processConfiguration(config);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Invalid JSON configuration: " + e.getMessage());
}
}
private void validateRequired(Map<String, String> data, String... fields) {
for (String field : fields) {
String value = data.get(field);
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException("Required field missing or empty: " + field);
}
}
}
private boolean isValidEmail(String email) {
return email != null && email.contains("@") && email.contains(".");
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-cucumber--cucumber-java