Skip to main content

Scenario Outline

Scenario Outline with an Examples table maps to JUnit's @ParameterizedTest + @CsvSource.

Source: examples/getting-started/example-6 on GitHub.

What this demonstrates​

  • Scenario Outline → @ParameterizedTest (instead of @Test)
  • Examples table rows → @CsvSource(textBlock = …) data
  • Placeholders (<name>, <price>) become method parameters
  • Column headers become parameter names (sanitised to valid Java identifiers — expected subtotal → expectedSubtotal)
  • Type inference is applied per column across all rows
  • Multiple Examples blocks → multiple repeatable @CsvSource annotations on the same method
  • Outline step methods are implemented once in the concrete subclass like any other step — each Examples row passes its values in as arguments, so one implementation runs once per row

Gherkin → JUnit mapping​

GherkinJUnit
Scenario Outline:@ParameterizedTest
Examples: table@CsvSource(textBlock = …)
<placeholder> in stepsMethod parameter
Column headerParameter name
Table rowOne test iteration

Generated output​

public abstract class ShoppingCartScenarios extends ShoppingCartFeature {

public abstract void myCartContains$p1WithQuantity$p2AndUnitPrice$p3(String p1, Integer p2, Double p3);

public abstract void iChangeTheQuantityTo$p1(Integer p1);

public abstract void theCartSubtotalShouldBe$p1(Double p1);

@ParameterizedTest(name = "Example: [{arguments}]")
@CsvSource(
useHeadersInDisplayName = true,
delimiter = '|',
textBlock = """
name | start qty | price | new qty | expected subtotal
Wireless Headphones | 1 | 60.00 | 2 | 120.00
Coffee Beans 1kg | 2 | 15.50 | 3 | 46.50
USB-C Cable | 1 | 8.99 | 5 | 44.95
"""
)
@Order(1)
@DisplayName("Scenario Outline: Subtotal updates when quantity changes")
public void scenario_1(String name, Integer startQty, Double price, Integer newQty,
Double expectedSubtotal) {
/*
* Given my cart contains <name> with quantity <start qty> and unit price <price>
*/
myCartContains$p1WithQuantity$p2AndUnitPrice$p3(name, startQty, price);
/*
* When I change the quantity to <new qty>
*/
iChangeTheQuantityTo$p1(newQty);
/*
* Then the cart subtotal should be <expected subtotal>
*/
theCartSubtotalShouldBe$p1(expectedSubtotal);
}
}

The original step text — placeholders and all — is preserved verbatim in a block comment above each call, and each generated method carries an @Order annotation so the scenarios run in feature-file order.

When multiple Examples blocks are present, each block produces its own repeatable @CsvSource annotation on the same @ParameterizedTest method, with the (optional) Examples title kept as a block comment above it:

/*
* Examples: Domestic orders
*/
@CsvSource(
useHeadersInDisplayName = true,
delimiter = '|',
textBlock = """
subtotal | shipping cost
30.00 | 5.99
50.00 | 0.00
75.00 | 0.00
"""
)
/*
* Examples: Small orders
*/
@CsvSource(
useHeadersInDisplayName = true,
delimiter = '|',
textBlock = """
subtotal | shipping cost
10.00 | 8.99
5.00 | 8.99
"""
)
public void scenario_2(Double subtotal, Double shippingCost) { /* ... */ }

The test class​

The concrete ShoppingCartTest implements the outline steps exactly like any other step. The generated @ParameterizedTest method passes each row's values in, so one implementation covers every row:

ShoppingCartTest.java
@Override
public void myCartContains$p1WithQuantity$p2AndUnitPrice$p3(String name, Integer quantity, Double unitPrice) {
item = new CartItem(name, quantity, unitPrice);
subtotal = item.quantity() * item.unitPrice();
}

@Override
public void theCartSubtotalShouldBe$p1(Double expectedSubtotal) {
assertEquals(expectedSubtotal, subtotal, 0.001);
}

Run it​

cd examples/getting-started/example-6
mvn test