Once feature files and step definitions are in place, scenarios run via the
usual cargo test command. Test functions created by the #[scenario] macro
behave like other rstest tests; they honour #[tokio::test] or
#[async_std::test] attributes if applied to the original function. Each
scenario runs its steps sequentially in the order defined in the feature file.
By default, missing steps emit a compile‑time warning and are checked again at
runtime, so steps can live in other crates. Enabling the
compile-time-validation feature on rstest-bdd-macros registers steps and
performs compile‑time validation, emitting warnings for any that are missing.
The strict-compile-time-validation feature builds on this and turns those
warnings into compile_error!s when all step definitions are local. This
prevents behaviour specifications from silently drifting from the code while
still permitting cross‑crate step sharing.
To enable validation, pin a feature in the project's dev-dependencies:
[dev-dependencies]
rstest-bdd-macros = { version = "0.1.0", features = ["compile-time-validation"] }
For strict checking use:
[dev-dependencies]
rstest-bdd-macros = { version = "0.1.0", features = ["strict-compile-time-validation"] }
Steps are only validated when one of these features is enabled.
Best practices for writing effective scenarios include:
-
Keep scenarios focused. Each scenario should test a single behaviour and contain exactly one
Whenstep. If multiple actions need to be tested, break them into separate scenarios. -
Make outcomes observable. Assertions in
Thensteps should verify externally visible results such as UI messages or API responses, not internal state or database rows. -
Avoid user interactions in
Givensteps.Givensteps establish context but should not perform actions. -
Write feature files collaboratively. The value of Gherkin lies in the conversation between the three amigos; ensure that business stakeholders read and contribute to the feature files.
-
Use placeholders for dynamic values. Pattern strings may include
format!-style placeholders such as{count:u32}. Type hints narrow the match. Numeric hints support all Rust primitives (u8..u128,i8..i128,usize,isize,f32,f64). Floating-point hints accept integers, decimal forms with optional leading or trailing digits, scientific notation (for example,1e3,-1E-9), and the special valuesNaN,inf, andInfinity(matched case-insensitively). Matching is anchored: the entire step text must match the pattern; partial matches do not succeed. Escape literal braces with{{and}}. Use\to match a single backslash. A trailing\or any other backslash escape is treated literally, so\dmatches the two-character sequence\d. Nested braces inside placeholders are not supported. Braces are not allowed inside type hints. Placeholders use{name}or{name:type}; the type hint must not contain braces (for example,{n:{u32}}and{n:Vec<{u32}>}are rejected). To describe braces in the surrounding step text (for example, referring to{u32}), escape them as{{and}}rather than placing them inside{name:type}. The lexer closes the placeholder at the first}after the optional type hint; any characters between the:typeand that first}are ignored (for example,{n:u32 extra}parses asname = n,type = u32).namemust start with a letter or underscore and may contain letters, digits, or underscores ([A-Za-z_][A-Za-z0-9_]*). Whitespace within the type hint is ignored (for example,{count: u32}and{count:u32}are both accepted), but whitespace is not allowed between the name and the colon. Prefer the compact form{count:u32}in new code. When a pattern contains no placeholders, the step text must match exactly. Unknown type hints are treated as generic placeholders and capture any non-newline text using a non-greedy match.