Skip to content

Guides · 2 of 7

Define steps

A step definition is an ordinary Rust function with a pattern on it. The pattern decides which lines it runs for and what it pulls out of them.

Patterns

#[given], #[when], and #[then] each take the text of the step they implement, without its keyword. A pattern matches the whole line, not a prefix of it, and an And or But step is matched against the keyword it continues.

tests/lantern.rs
#[given("a lantern on the trolley")]
fn load(trolley: &mut Trolley) {
    trolley.loaded = true;
    trolley.upright = true;
}

Compiled and run against rstest-bdd 0.6.0.

Parameters that are not placeholders are fixtures, found by name: trolley here is the trolley fixture. Fixtures and state covers them.

Typed placeholders

{name} in a pattern captures text into the parameter of the same name. {name:Type} also constrains what matches and parses it through FromStr before the step runs, so the function receives a number rather than a string.

tests/counting.rs
#[given("a trolley carrying {count:u32} lanterns")]
fn load(trolley: &mut Trolley, count: u32) {
    trolley.lanterns = count;
}

Compiled and run against rstest-bdd 0.6.0.

Quotes in the step are part of the text, so a pattern that expects them says so, and the placeholder captures what is between them:

tests/weather.rs
#[given("the forecast is \"{forecast}\"")]

Compiled and run against rstest-bdd 0.6.0.

Inferred patterns and expr

A step attribute with no pattern takes it from the function’s name, with underscores read as spaces. A pattern may also be given as expr = "…", the form cucumber-rs uses, which eases moving a suite across.

tests/features/bell.feature
Feature: Ringing the bell
  Scenario: Ring it three times
    Given a bell on the trolley
    When the bell rings 3 times
    Then the rabbit hears 3 rings

Compiled and run against rstest-bdd 0.6.0.

tests/bell.rs
//! A step pattern inferred from its function name, and a cucumber-rs-style
//! `expr =` pattern with a typed placeholder.

use rstest::fixture;
use rstest_bdd_macros::{given, scenario, then, when};

#[derive(Default)]
struct Bell {
    rings: u32,
}

#[fixture]
fn bell() -> Bell {
    Bell::default()
}

// No pattern: it is inferred from the function name, underscores as spaces.
#[given]
fn a_bell_on_the_trolley(bell: &mut Bell) {
    bell.rings = 0;
}

// cucumber-rs style, accepted to ease a migration.
#[when(expr = "the bell rings {times:u32} times")]
fn ring(bell: &mut Bell, times: u32) {
    bell.rings += times;
}

#[then("the rabbit hears {count:u32} rings")]
fn hears(bell: &Bell, count: u32) {
    assert_eq!(bell.rings, count);
}

#[scenario(path = "tests/features/bell.feature")]
fn ring_three_times(bell: Bell) {
    let _ = bell;
}

Compiled and run against rstest-bdd 0.6.0.

Steps that return

A step may return a value instead of mutating a fixture. The value replaces the one fixture of the same type for the steps that follow. A Result is unwrapped: Ok is stored, and Err fails the scenario at that step.

tests/features/shortbread.feature
Feature: Sharing the shortbread
  Scenario: One biscuit each
    Given a tin of 12 shortbread biscuits
    When 3 friends take one each
    Then 9 biscuits remain in the tin

Compiled and run against rstest-bdd 0.6.0.

tests/shortbread.rs
//! Steps that return an updated fixture value, including a fallible `when`
//! step that returns `Result<Tin, String>`.

use rstest::fixture;
use rstest_bdd_macros::{given, scenario, then, when};

#[derive(Clone, Copy, Debug, Default, PartialEq)]
struct Tin(u32);

#[fixture]
fn tin() -> Tin {
    Tin::default()
}

#[given("a tin of {count:u32} shortbread biscuits")]
fn filled(count: u32) -> Tin {
    Tin(count)
}

#[when("{friends:u32} friends take one each")]
fn share(tin: Tin, friends: u32) -> Result<Tin, String> {
    tin.0
        .checked_sub(friends)
        .map(Tin)
        .ok_or_else(|| format!("only {} biscuits for {friends} friends", tin.0))
}

#[then("{left:u32} biscuits remain in the tin")]
fn remain(tin: Tin, left: u32) {
    assert_eq!(tin, Tin(left));
}

#[scenario(path = "tests/features/shortbread.feature")]
fn sharing(tin: Tin) {
    let _ = tin;
}

Compiled and run against rstest-bdd 0.6.0.

Here the Given step fills the tin, the When step hands back a new one, and the Then step reads the latest. Were four friends to arrive at a tin of three, the When step would fail with “only 3 biscuits for 4 friends”.

Fine print