Skip to main content

Scenario outlines

A scenario outline runs the same scenario once per example row. It is the BDTest equivalent of a Gherkin Scenario Outline with an Examples table, written with typed C# objects.

Illustrative fragment
[Test]
public async Task AddingNumbers()
{
await ScenarioOutline("Adding numbers", new[]
{
new { Left = 1, Right = 2, Expected = 3 },
new { Left = 4, Right = 5, Expected = 9 },
}, row => Given(() => Calculator.SetNumbers(row.Left, row.Right))
.WithStepText(() => "numbers <Left> and <Right>")
.When(() => Calculator.AddNumbers())
.Then(() => Calculator.TheResultIs(row.Expected))
.WithArgs(() => row.Expected));
}

ScenarioOutline(scenarioText, examples, factory) takes:

ParameterDescription
scenarioTextThe outline's scenario name in reports. Placeholders apply to step text, not to this name.
examplesAny IEnumerable<TExample>: anonymous objects, records or classes. At least one row is required.
factoryA function that returns a new runnable chain for one row.

Await the outline directly or call BDTestAsync(). Pass an execution token with BDTestAsync(cancellationToken) to cancel preparation and row execution. Like a normal chain, an outline runs once.

Example tables and placeholders​

Public property names of the example type become the columns of the example table. <Name> placeholders in step text, including [StepText] and .WithStepText(...), are replaced with that row's values:

Illustrative fragment
[StepText("I log in as <Name> with role <Role>")]
public Task LogIn(string name, string role) { ... }

public record Login(string Name, string Role);

await ScenarioOutline("Users can sign in", new[]
{
new Login("Alice", "Admin"),
new Login("Bob", "Reader"),
}, row => When(() => Users.LogIn(row.Name, row.Role))
.WithArgs(() => row.Name, () => row.Role)
.Then(() => Users.TheDashboardIsShown()));

row is a lambda parameter, so the source generator cannot read row.Name automatically. Add .WithArgs(...) to steps that take row values; without it, the step shows its source text instead of the [StepText] template.

  • Values are formatted with the invariant culture and captured when the outline is built.
  • Unknown placeholders stay unchanged.
  • null values display as null.

How rows run​

  • Rows run one after another, each with its own steps, output, status and retry count.
  • Each row starts with a fresh Context, and ConfigureContext runs for it. The factory sees the newly configured context. After the factory builds the row, BDTest evaluates its scenario skip rules once, then awaits IBDTestContextLifetime.InitializeAsync before scenario hooks and steps. A throwing skip predicate fails the row without initialization, hooks or retries, even when its exception type is registered as success. A matching skip still initializes the context and runs scenario hooks, as for an ordinary scenario. Your framework's setup and teardown run once for the whole outline.
  • While the factory runs and the context is amended, BDTestContext.CurrentScenario is null because the row's scenario does not exist yet. Steps and retry amendments see the executing row.
  • The factory must return a new chain from the same test instance for each row.

Results​

Ordinary failures do not stop later rows. Explicit or adapter cancellation stops subsequent rows. So does work or a cancellation callback that remains unfinished after the cancellation grace period, to avoid racing a replacement context.

Cancellation between rows fails the outline with an OperationCanceledException, even if every completed row passed. Completed rows keep their recorded outcomes; the unstarted rows do not run or replace the context.

Otherwise, the first exception for the most severe outcome is rethrown to the test runner:

  • Failure, not-implemented and inconclusive rows take priority, in that order.
  • Passed rows mixed with skipped rows pass.
  • If every row is skipped, the outline is skipped.

JSON, HTML and report server output contain one outline with an example table and per-row results. BDTestUtil.GetScenarios(run) returns the outline scenario: IsOutline is true, and its Examples collection holds the row scenarios with their ExampleValues. Each row has an OutlineId that points back to its parent.