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.