Skip to main content

Typed Data Tables

The default LIST_OF_OBJECT_PARAMS data-table mode — Gherkin data tables generate type-safe inner classes with typed accessors. No Cucumber dependency.

Source: examples/common-use-cases/example-1 on GitHub.

What this demonstrates​

  • Data tables generate inner Param classes (named after the last word of the step text + Param)
  • Column headers become typed fields with automatic type inference
  • Per-column inference: Integer, Double, Boolean, String, …
  • Readable headers (e.g. unit price) are sanitised to Java field names (unitPrice)
  • Each row becomes a new XxxParam(...) call inside List.of(...)
  • Multiple data tables in the same feature produce separate inner classes
  • The concrete test reads rows through the typed accessors — no string parsing

The Gherkin​

Given my cart contains the following products:
| name | qty | unit price | in stock |
| Wireless Headphones | 1 | 59.99 | true |
| Coffee Beans 1kg | 3 | 12.50 | true |
| USB-C Cable | 2 | 8.99 | false |

The spec, ShoppingCart.specb, sits next to its marker class in src/test/java, so the marker needs only a bare @Gherkin2JUnit — the processor finds the spec by convention.

The generated inner class​

public static class ProductsParam {
private final String name;
private final Integer qty;
private final Double unitPrice;
private final Boolean inStock;

public ProductsParam(String name, Integer qty, Double unitPrice, Boolean inStock) {
this.name = name;
this.qty = qty;
this.unitPrice = unitPrice;
this.inStock = inStock;
}

public String name() { return this.name; }
public Integer qty() { return this.qty; }
public Double unitPrice() { return this.unitPrice; }
public Boolean inStock() { return this.inStock; }
}

The generated step method and call site​

// Step method signature
public abstract void myCartContainsTheFollowingProducts(List<ProductsParam> products);

// Generated call site
myCartContainsTheFollowingProducts(
List.of(
new ProductsParam("Wireless Headphones", 1, 59.99, true),
new ProductsParam("Coffee Beans 1kg", 3, 12.50, true),
new ProductsParam("USB-C Cable", 2, 8.99, false)
));

Implementing the steps​

The concrete ShoppingCartTest extends the generated ShoppingCartScenarios and works with the rows as typed objects — product.qty() is an Integer, product.inStock() a Boolean:

public class ShoppingCartTest extends ShoppingCartScenarios {

private List<ProductsParam> products;
private double subtotal;

@Override
public void myCartContainsTheFollowingProducts(List<ProductsParam> products) {
this.products = products;
}

@Override
public void iCalculateTheSubtotal() {
subtotal = products.stream()
.mapToDouble(product -> product.qty() * product.unitPrice())
.sum();
}

@Override
public void theCartSubtotalShouldBe$p1(Double expectedSubtotal) {
assertEquals(expectedSubtotal, subtotal, 0.001);
}

// … the stock-check and bulk-discount steps work the same way
}
ShoppingCartFeature.java          (marker class, bare @Gherkin2JUnit)
└→ ShoppingCartScenarios.java (generated, abstract — the Param classes and @Test methods)
└→ ShoppingCartTest.java (your concrete class, implements the steps)

Key points​

  • Each unique data table shape in a feature produces its own Param class
  • Field accessors use record-style names (name(), not getName())
  • No Cucumber dependency — these are plain Java classes
  • For compile-time enum safety on a column, see Type Refinement with Enums

Run it​

cd examples/common-use-cases/example-1
mvn test