Concrete Mode
Concrete mode is the opt-out from the default. Instead of an abstract …Scenarios class you extend, the generator emits a concrete, directly-runnable …Test class. Unimplemented steps become failing stubs at run time, not compile errors.
Source: examples/going-further/example-1 on GitHub.
The default is abstract mode (
shouldBeAbstract = true), shown throughout the earlier examples: an abstract…Scenariosclass with one abstract method per step, enforced at compile time. This page is about opting out of that.
What this demonstrates
@Gherkin2JUnitOptions(shouldBeAbstract = false)generates a concrete class- The generated class suffix is
Testinstead ofScenarios(configurable) - No subclass required — the generated class is runnable on its own
- Step methods are implemented directly in the marker class; the generated class inherits them
- Unimplemented steps become failing stubs (
Assertions.fail(...)) — a runtime failure, not a compile error - Methods already implemented on the marker class are inherited and not stubbed
The class hierarchy
Concrete mode collapses the three-layer hierarchy of abstract mode into two layers — there is no hand-written subclass:
ShoppingCartFeature.java (marker class, @Gherkin2JUnit + shouldBeAbstract = false, implements steps)
└→ ShoppingCartTest.java (generated, concrete, runnable — inherits your implementations)
The marker class
Implement the step methods directly on the marker class. Abstract mode is the default, so you opt into concrete mode explicitly:
@Gherkin2JUnitOptions(shouldBeAbstract = false)
@Gherkin2JUnit("specs/ShoppingCart.specb")
public abstract class ShoppingCartFeature {
private final List<CartItem> cart = new ArrayList<>();
public void iHaveAnEmptyShoppingCart() {
cart.clear();
}
public void iAdd$p1WithQuantity$p2AndUnitPrice$p3(String name, Integer quantity, Double unitPrice) {
cart.add(new CartItem(name, quantity, unitPrice));
}
// … remaining steps …
record CartItem(String name, int quantity, double unitPrice) {}
}
Generated output
The generated ShoppingCartTest is concrete (note: no abstract keyword) and inherits the step methods you implemented on the marker. Any step you did not implement is emitted as a failing stub instead of an abstract declaration:
@DisplayName("Feature: ShoppingCart")
@Generated("dev.specbinder.processor.AnnotationProcessor")
public class ShoppingCartTest extends ShoppingCartFeature {
// A step NOT implemented on the marker class becomes a failing stub:
public void someUnimplementedStep() {
Assertions.fail("Step is not yet implemented");
}
@Test
@DisplayName("Scenario: Add item and verify cart contents")
public void scenario_1() {
/*
* Given I have an empty shopping cart
*/
iHaveAnEmptyShoppingCart();
// … remaining step calls …
}
}
Because the class is concrete, JUnit can instantiate and run it directly — no subclass needed. The trade-off: a forgotten step only surfaces when the test runs (the stub calls Assertions.fail(...)), whereas abstract mode would refuse to compile.
Abstract vs concrete mode
| Aspect | Abstract (default) | Concrete |
|---|---|---|
| Generated class | abstract, extends marker | Concrete, extends marker |
| New (unimplemented) step methods | abstract declarations | Failing stubs (Assertions.fail(...)) |
| Inherited step methods | Not re-declared | Not re-declared |
| Missing implementations | Compile error | Runtime failure |
| Where you implement | Marker class or a concrete subclass | Marker class |
| Subclass required to run | Yes | No |
| Class suffix | Scenarios | Test |
| Opt in via | (default) | @Gherkin2JUnitOptions(shouldBeAbstract = false) |
In both modes, methods already present on the marker class are inherited — the generator emits neither a stub nor an abstract declaration for them. The difference is only in how new (unimplemented) steps are handled, and whether a subclass is needed to run the tests.
Tuning the stub behavior.
unimplementedStepBehaviorcontrols what an unimplemented stub does:FAIL(default) callsAssertions.fail(...),SKIPmarks the test as skipped viaAssumptions.assumeTrue(false, …), andCOMPILATION_ERRORemits invalid code so a missing step blocks compilation even in concrete mode.
Run it
cd examples/going-further/example-1
mvn test