Type Refinement with Enums
Refine generated String types to enums — in data-table Param classes and in quoted step parameters alike — to turn data drift into a compile error.
Source: examples/common-use-cases/example-4 on GitHub.
What this demonstrates
- Move the generated
XxxParamclass and the abstract step methods that deal with it up into the marker class - Change a
Paramfield fromStringto a custom enum - Change a quoted step parameter from
Stringto the same enum - The generator detects the existing class and step declarations and uses them instead of generating its own
- Invalid values in the feature file become compilation errors
- The step implementations stay in the concrete test, where the values already arrive as enum constants
Step 1 — Compile once, look at what was generated
On first compilation, the generator emits ProductsParam with String/inferred types, together with the abstract
step methods — including the one taking the category in quotes (When I filter by category "electronics"):
// Generated (before refinement)
public abstract void myCartContainsTheFollowingProducts(List<ProductsParam> products);
public abstract void iFilterByCategory$p1(String p1); // ← String by default
public static class ProductsParam {
private final String name;
private final Integer qty;
private final Double unitPrice;
private final String category; // ← String by default
// ...
}
Step 2 — Refine in the marker class
Move ProductsParam and the step declarations that deal with categories into the marker class, then change both the
category field and the filter step's parameter to an enum. Keeping them together makes it clear that the refined type
and the steps using it are now yours rather than generated:
@Gherkin2JUnit
public abstract class ShoppingCartFeature {
public enum Category { electronics, grocery, sports }
public static class ProductsParam {
// ...
private final Category category; // ← now an enum
// ...
}
public abstract void myCartContainsTheFollowingProducts(List<ProductsParam> products);
public abstract void iFilterByCategory$p1(Category category); // ← now an enum
}
The steps stay abstract — the concrete ShoppingCartTest implements them like any other step, and receives a
Category directly instead of parsing a String.
Step 3 — Compile-time safety
The generator now uses your ProductsParam and step declarations — it no longer emits any of them. The generated call
sites pass enum constants, statically imported from Category, for the table cells and the quoted value alike:
new ProductsParam("Wireless Headphones", 1, 59.99, electronics)
new ProductsParam("Coffee Beans 1kg", 3, 12.50, grocery)
iFilterByCategory$p1(electronics);
Step 4 — Catch drift
If someone adds a row with an unknown category:
| Yoga Mat | 1 | 25.00 | fitness |
or filters by one:
When I filter by category "fitness"
The generated code refers to fitness, which Category has no constant for — compilation error. The spec/data
mismatch is caught before tests run.
Run it
cd examples/common-use-cases/example-4
mvn test