Q16 of 21 · BDD / Cucumber
How do you transform data tables into typed domain objects in Cucumber?
BDD / CucumberSeniorbddcucumberdata-tablestype-conversionadvanced
Short answer
Short answer: Use DataTable.asMaps() for a list of string maps, or register a DataTableType (Java) / defineDataTableType (JS) to automatically convert table rows into strongly-typed domain objects.
Detail
Raw asMaps approach (simple, weakly typed):
@Given("the following users exist:")
public void usersExist(DataTable table) {
List<Map<String, String>> rows = table.asMaps();
for (var row : rows) {
db.insertUser(row.get("email"), row.get("role"));
}
}
Typed transformation (Java — register a DataTableType):
// In a configuration class / @BeforeAll
@DataTableType
public User userEntry(Map<String, String> entry) {
return new User(
entry.get("email"),
entry.get("role"),
Integer.parseInt(entry.getOrDefault("age", "0"))
);
}
// Now the step definition can use List<User> directly:
@Given("the following users exist:")
public void usersExist(List<User> users) {
users.forEach(db::insertUser);
}
JavaScript — defineDataTableType:
const { defineDataTableType } = require('@cucumber/cucumber');
defineDataTableType({
name: 'product',
regexp: /product/, // not needed for table types — use fromDataTable
from(rows) {
return rows.map(([name, price]) => ({ name, price: parseFloat(price) }));
},
});
Or simply use dataTable.hashes() for a map-per-row, which is usually enough.
Why typed transformation matters: When your domain objects have int/boolean/date fields, string maps force you to parse in every step. A registered DataTableType centralises the parsing and makes step definitions clean.
// WHAT INTERVIEWERS LOOK FOR
asMaps/hashes for simple cases. @DataTableType / defineDataTableType for typed conversion. Why typed is better (central parsing, clean step defs).