Execution Reporter
The dev.specbinder:execution-reporter module is a JUnit 5 extension that writes one JSON file per test class to the project's build output directory. You opt a feature in by annotating its marker class with @ExtendWith(SpecBinderReporter.class). Useful for CI dashboards, custom reporting, or any tooling that needs structured spec-execution data.
Source: examples/going-further/example-4 on GitHub.
What this demonstrates
- Activated explicitly per feature with
@ExtendWith(SpecBinderReporter.class)on the marker class - Each test class gets its own JSON report under
target/specbinder-reports/..., named after the concrete test class - The report mirrors the Gherkin hierarchy:
feature → rules → scenarios → outlines → examples - Pass / fail / aborted statuses for every node, with full error blocks for failures
- Non-SpecBinder tests are silently ignored
- Two generation options shape how rich the report is:
descriptionAsAnnotation(opt-in — Gherkin descriptions in the JSON) andemitScenarioHash(on by default — verbatim step text + typed arguments)
Activation
Add the dependency:
<dependency>
<groupId>dev.specbinder</groupId>
<artifactId>execution-reporter</artifactId>
<version>2026.47.0</version>
<scope>test</scope>
</dependency>
Then activate the reporter by annotating the marker class with @ExtendWith(SpecBinderReporter.class):
@Gherkin2JUnit
@ExtendWith(SpecBinderReporter.class)
public abstract class ShoppingCartFeature { … }
The spec, ShoppingCart.specb, sits next to the marker in src/test/java, so a bare @Gherkin2JUnit finds it by convention.
Because JUnit's @ExtendWith is @Inherited, placing it on the abstract marker means the concrete ShoppingCartTest that JUnit runs inherits it — so a single annotation covers the whole feature. Only classes carrying the extension produce a report; every other test is left untouched.
The class hierarchy
The step methods live in a concrete test class, as in the other examples — that's also the class JUnit runs, and the one the report is named after:
ShoppingCartFeature.java (marker, @Gherkin2JUnit + @ExtendWith(SpecBinderReporter.class))
└→ ShoppingCartScenarios.java (generated, abstract — @Description and @ScenarioHash on each scenario)
└→ ShoppingCartTest.java (your concrete class, implements the steps — the class JUnit runs)
ShoppingCartTest is where the deliberately failing and aborted scenarios come from:
public class ShoppingCartTest extends ShoppingCartScenarios {
private final Cart cart = new Cart();
@Override
public void theCartTotalShouldBe$p1(Double expected) {
assertEquals(expected, cart.subtotal(), 0.001);
}
@Override
public void theUpstreamPricingServiceIsUnavailable() {
Assumptions.abort("pricing service is unreachable; skipping scenario");
}
// … the remaining cart steps …
}
Running it
cd examples/going-further/example-4
mvn test
After the run, look at:
target/specbinder-reports/dev/specbinder/examples/goingfurther/reporter/ShoppingCartTest.json
The path is the fully qualified name of the concrete test class, as directories.
Report schema
The schema is hierarchical and uses the same displayName field name at every node:
feature
├── scenarios[] ← plain scenarios + Scenario Outlines under the feature
└── rules[]
└── scenarios[] ← scenarios that live under a Rule
The example feature deliberately exercises every code path the listener handles:
- Several plain scenarios that pass (real assertions in
ShoppingCartTestagainst an in-memory cart) - One scenario with a wrong expected total →
status: "failed", populatederrorblock - One scenario whose
GivencallsAssumptions.abort(...)→status: "aborted" - A
Rulewith two scenarios → routed under the report'srules[]branch - A
Scenario Outlinewith three rows → ascenarioOutlinenode with threeexamples[]entries
Enriching the report
Two @Gherkin2JUnitOptions flags decide whether the JSON report carries more than just statuses and timings. One of them is already on for you:
@Gherkin2JUnit
@Gherkin2JUnitOptions(descriptionAsAnnotation = true)
@ExtendWith(SpecBinderReporter.class)
public abstract class ShoppingCartFeature { … }
descriptionAsAnnotation = trueemits Gherkin description text — under the Feature, Rule, or Scenario — as runtime-retained@Descriptionannotations instead of JavaDoc. The reporter reads them via reflection and adds adescriptionfield at each level of the JSON. This feature'sFeature:andRule:blocks both carry a description, so the report shows one at both levels. It defaults tofalse; the reporter simply omits thedescriptionfields until you turn it on.emitScenarioHash— on by default, so there's nothing to switch on. It stamps each generated scenario method with a@ScenarioHash: a content hash of its executable steps. Downstream tooling can compare it against live source to detect when a scenario has drifted since the recorded run.