Summary

Version 0.6.0 Updated Sep 14, 2026

rstest‑bdd seeks to bring the collaborative clarity of Behaviour‑Driven Development to Rust without sacrificing the ergonomics of rstest and the convenience of cargo test. In its present form, the framework provides a core workflow: write Gherkin scenarios, implement matching Rust functions with #[given], #[when] and #[then] annotations, rely on matching parameter names for fixture injection (use #[from] when renaming), and bind tests to scenarios with #[scenario]. Step definitions are discovered at link time via the inventory crate, and scenarios execute all steps in sequence before running any remaining test code. While advanced Gherkin constructs and parameterization remain on the horizon, this foundation allows teams to integrate acceptance criteria into their Rust test suites and to engage all three amigos in the specification process.

Feature file rebuild invalidation

Since v0.6.0, #[scenario] and scenarios! register every bound .feature file as a Cargo rebuild dependency: the macro emits an include_bytes! binding whose path is built from CARGO_MANIFEST_DIR, so rustc records the file in dep-info and Cargo recompiles the scenario binary whenever the file changes. Editing only a .feature file therefore recompiles the crate, and the tests reflect the new text immediately.

#[scenario] tracks its single bound file. scenarios! tracks every .feature file it discovers in the bound directory — including files whose scenarios are excluded by a tags = filter.

Generated scenario metadata renders feature paths as manifest-relative paths with / separators when the file is inside the consuming crate's manifest directory. A file outside that directory remains absolute when it shares the same filesystem root; an unrelated root is rejected at compile time. The rendered value is carried by ScenarioMetadata::feature_path and appears in JSON and JUnit reports and in cargo bdd --dump-steps output.

Per-file tracking covers edits to files that existed when the macro ran. It cannot see a file that is added afterwards, because nothing referenced a file that did not exist at expansion time. To close that gap, add a build.rs to the consuming crate. It tells Cargo to rerun the build script whenever the bound directory changes:

fn main() {
    println!("cargo::rerun-if-changed=tests/features");
}

Cargo scans a rerun-if-changed directory recursively, so a single directory line covers new files and subdirectories; a hand-maintained per-file list can silently omit a new subdirectory, which is the failure mode this mechanism exists to prevent. The snippet above is executed by this repository's test suite (it is extracted from this document and run against a scratch crate, see the [developers' guide][developers-guide]), so it cannot rot.

Cost

Per-file tracking makes a previously free operation cost something: editing one .feature file now rebuilds the whole test binary containing the scenarios! invocation that binds it, which re-parses every feature file in the bound directory. Measured at roughly 5.4 s for 100 files with five scenarios each on a development host. For large feature directories, split them across several test binaries with disjoint scenarios! invocations.