Skip to main content

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) and emitScenarioHash (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 ShoppingCartTest against an in-memory cart)
  • One scenario with a wrong expected total → status: "failed", populated error block
  • One scenario whose Given calls Assumptions.abort(...) → status: "aborted"
  • A Rule with two scenarios → routed under the report's rules[] branch
  • A Scenario Outline with three rows → a scenarioOutline node with three examples[] 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 = true emits Gherkin description text — under the Feature, Rule, or Scenario — as runtime-retained @Description annotations instead of JavaDoc. The reporter reads them via reflection and adds a description field at each level of the JSON. This feature's Feature: and Rule: blocks both carry a description, so the report shows one at both levels. It defaults to false; the reporter simply omits the description fields 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.