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 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 | Crate | Runs the scenario | Step context | Use 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].
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.
//! 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”.
#[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.
//! 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.
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.