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).