Autodiscovering scenarios

Version 0.5.0 Updated Feb 09, 2026

For large suites, it is tedious to bind each scenario manually. The scenarios! macro scans a directory recursively for .feature files and generates a module with a test for every Scenario found. Each test is named after the feature file and scenario title. Identifiers are sanitized (ASCII-only) and deduplicated by appending a numeric suffix when collisions occur.

use rstest_bdd_macros::{given, then, when, scenarios};

#[given("a precondition")] fn precondition() {}
#[when("an action occurs")] fn action() {}
#[then("events are recorded")] fn events() {}

scenarios!("tests/features/auto");

// Only expand scenarios tagged @smoke and not marked @wip
scenarios!("tests/features/auto", tags = "@smoke and not @wip");

When tags is supplied the macro evaluates the expression against the same union of feature, scenario, and example tags described above. Scenarios that do not match simply do not generate a test, and outline examples drop unmatched rows.

Fixture injection with `scenarios!`

The fixtures = [name: Type, ...] parameter injects fixtures into all generated scenario tests. Fixtures are bound via rstest and inserted into the step context, making them available to step functions that declare the corresponding parameter.

use rstest::fixture;
use rstest_bdd_macros::{given, scenarios};

struct TestWorld { value: i32 }

#[fixture]
fn world() -> TestWorld { TestWorld { value: 42 } }

#[given("a precondition")]
fn step_uses_world(world: &TestWorld) {
    assert_eq!(world.value, 42);
}

scenarios!("tests/features/auto", fixtures = [world: TestWorld]);

The macro adds #[expect(unused_variables)] to generated test functions when fixtures are present, preventing lint warnings since fixture parameters are consumed via StepContext rather than referenced directly in the test body.