Skip to main content

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 XxxParam class and the abstract step methods that deal with it up into the marker class
  • Change a Param field from String to a custom enum
  • Change a quoted step parameter from String to 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