Skip to content

New in 0.6.0

Keep the story. Choose its stage.

A harness adapter owns the environment a scenario runs in. The feature file and the steps stay the same; harness = picks the stage.

The rabbit, Marrow, and the robot push a toy trolley with an upright glowing lantern across a patchwork quilt towards three felt puppet-theatre stages: oatmeal wool, mustard with brass cogs, and night blue with embroidered stars.
Three stages Clover, Marrow, and Bobbin take the same rehearsal to a new stage.

The stages

Without harness, a scenario runs on the test thread. With it, the generated test hands the whole run — steps, skip handling, and the scenario body — to the adapter, which may set up a runtime or an application first. Three adapters ship with 0.6.0.

Harness adapters in rstest-bdd 0.6.0
HarnessCrateRuns the scenarioStep contextUse it when
StdHarness rstest-bdd-harness On the test thread None Always, by default; nothing to add.
TokioHarness rstest-bdd-harness-tokio In a Tokio current-thread runtime TokioTestContext Steps need the runtime handle or spawn tasks.
GpuiHarness rstest-bdd-harness-gpui Under #[gpui::test] gpui::TestAppContext The application is built on GPUI.
Your own Implements HarnessAdapter Wherever the adapter says Its Context type Anything else: a simulator, an ECS world.

For the first-party adapters the macro infers the matching test attributes from the harness path, so harness = … is the only argument needed. attributes = … exists to override that choice.

The Tokio stage

Add rstest-bdd-harness-tokio = "0.6.0" as a dev-dependency. A step reaches the runtime through a parameter marked #[harness_context].

tests/features/stage.feature
Feature: Choosing a stage
  Scenario: Rehearse on the Tokio stage
    Given the trolley is on the Tokio stage
    When the departure bell rings
    Then the lantern arrives upright

Compiled and run against rstest-bdd 0.6.0.

tests/stage.rs
//! The Tokio harness, with `#[harness_context]` exposing the runtime handle
//! to a step.

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

#[derive(Default)]
struct Trolley {
    arrived: bool,
    upright: bool,
}

#[fixture]
fn trolley() -> Trolley {
    Trolley::default()
}

#[given("the trolley is on the Tokio stage")]
fn on_stage(#[harness_context] stage: &TokioTestContext, trolley: &mut Trolley) {
    assert_eq!(stage.handle().id(), tokio::runtime::Handle::current().id());
    trolley.upright = true;
}

#[when("the departure bell rings")]
async fn depart(trolley: &mut Trolley) {
    trolley.arrived = true;
}

#[then("the lantern arrives upright")]
fn check(trolley: &Trolley) {
    assert!(trolley.arrived && trolley.upright);
}

#[scenario(
    path = "tests/features/stage.feature",
    harness = rstest_bdd_harness_tokio::TokioHarness,
)]
fn tokio_stage(trolley: Trolley) {
    let _ = trolley;
}

Compiled and run against rstest-bdd 0.6.0.

The GPUI stage

rstest-bdd-harness-gpui runs the scenario under GPUI’s test attribute and hands steps the gpui::TestAppContext through #[harness_context]. A fallible scenario body that returns Err fails with “scenario returned an error”.

From the user’s guide
#[scenario(
    path = "tests/features/counter.feature",
    name = "Increment a counter and observe GPUI context",
    harness = rstest_bdd_harness_gpui::GpuiHarness,
)]
fn increment_and_observe_gpui_context() -> Result<(), std::io::Error> {
    increment_counter()?;
    Ok(())
}

Quoted from the user’s guide; the sample crate does not build GPUI.

Scenarios that keep windows and entities alive between steps have their own playbook in the user’s guide, and the repository’s gpui-counter example runs one.

A stage of your own

An adapter implements HarnessAdapter from rstest-bdd-harness and Default, because the macro builds it. run receives the scenario as a request, sets the stage, and runs it. It returns HarnessResult<T>, so a stage that fails to build is an infrastructure error with its own message, not a failed assertion.

tests/own_stage.rs
//! A custom `HarnessAdapter` that announces the stage before running the
//! scenario on the test thread.

use rstest::fixture;
use rstest_bdd_harness::{HarnessAdapter, HarnessResult, StdScenarioRunRequest};
use rstest_bdd_macros::{given, scenario, then, when};

/// A harness that announces the stage, then runs the scenario on the test
/// thread, as the standard harness does.
#[derive(Default)]
struct LanternStage;

impl HarnessAdapter for LanternStage {
    type Context = ();

    fn run<T>(&self, request: StdScenarioRunRequest<'_, T>) -> HarnessResult<T> {
        eprintln!("lighting the lantern stage");
        Ok(request.run_without_context())
    }
}

#[derive(Default)]
struct Trolley {
    arrived: bool,
    upright: bool,
}

#[fixture]
fn trolley() -> Trolley {
    Trolley::default()
}

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

#[when("the departure bell rings")]
fn depart(trolley: &mut Trolley) {
    trolley.arrived = true;
}

#[then("the lantern arrives upright")]
fn check(trolley: &Trolley) {
    assert!(trolley.arrived && trolley.upright);
}

#[scenario(path = "tests/features/own_stage.feature", harness = LanternStage)]
fn lantern_stage(trolley: Trolley) {
    let _ = trolley;
}

Compiled and run against rstest-bdd 0.6.0.

cargo test --test own_stage -- --nocaptureexcerpt
running 1 test
lighting the lantern stage
test lantern_stage ... ok

An adapter with a context of its own sets Context and hands it to the steps that ask with #[harness_context]. The user’s guide has a cookbook for third-party adapters, with a conformance check for attribute policies.