pub struct Story { /* private fields */ }Expand description
A BDD story builder that captures steps and emits a RawTestCase on drop.
The status is detected on drop: a story dropped while the thread unwinds
from a panic records "fail", otherwise "pass". Every failing assertion
panics, so a passing test needs no extra call.
Tests that return Result are the exception. Returning Err fails the test
without panicking, so call Story::fail on that path, or use
Story::record_result, which does it for you.
Implementations§
Source§impl Story
impl Story
Sourcepub fn new(scenario: &str) -> Self
pub fn new(scenario: &str) -> Self
Create a new story with the given scenario description.
#[track_caller] so the scenario records the test file it was written
in, the same path declare_feature! records through file!(). The
report groups by source file: without it a scenario lands under
“unknown” and its file’s feature declaration never reaches it.
Set tags on the story (consumes and returns self for chaining at creation).
Sourcepub fn with_tickets(self, tickets: &[&str]) -> Self
pub fn with_tickets(self, tickets: &[&str]) -> Self
Set tickets on the story (consumes and returns self for chaining at creation).
Sourcepub fn with_covers(self, covers: &[&str]) -> Self
pub fn with_covers(self, covers: &[&str]) -> Self
Declare the product-code paths/globs this story exercises (consumes and returns self).
Sourcepub fn with_ticket_url(self, id: &str, url: &str) -> Self
pub fn with_ticket_url(self, id: &str, url: &str) -> Self
Add a ticket with a URL to the story (consumes and returns self for chaining).
Sourcepub fn with_trace_url_template(self, template: &str) -> Self
pub fn with_trace_url_template(self, template: &str) -> Self
Set the URL template for OTel trace links (uses {traceId} placeholder).
Also settable via OTEL_TRACE_URL_TEMPLATE env var.
Sourcepub fn assert_that(&mut self, text: &str) -> &mut Self
pub fn assert_that(&mut self, text: &str) -> &mut Self
Add an Assert step (alias for Then). Uses assert_that because assert! is a macro.
Sourcepub fn with_docs(&mut self, docs: Vec<StepDoc>) -> &mut Self
pub fn with_docs(&mut self, docs: Vec<StepDoc>) -> &mut Self
Attach inline documentation entries to the most recently added step.
Sourcepub fn start_timer(&mut self) -> usize
pub fn start_timer(&mut self) -> usize
Start a high-resolution timer tied to the current step. Returns a token to pass to end_timer().
Sourcepub fn end_timer(&mut self, token: usize)
pub fn end_timer(&mut self, token: usize)
Stop the timer and record duration_ms on the step that was active when start_timer() was called. Double-end is a no-op.
Sourcepub fn fn_step<F, T>(&mut self, keyword: &str, text: &str, body: F) -> Twhere
F: FnOnce() -> T,
pub fn fn_step<F, T>(&mut self, keyword: &str, text: &str, body: F) -> Twhere
F: FnOnce() -> T,
Wrap a closure as a BDD step with automatic timing.
Creates a step marked as wrapped=true, executes the body,
records duration_ms, and re-panics if the body panicked.
Sourcepub fn expect_step<F, T>(&mut self, text: &str, body: F) -> Twhere
F: FnOnce() -> T,
pub fn expect_step<F, T>(&mut self, text: &str, body: F) -> Twhere
F: FnOnce() -> T,
Shorthand for fn_step("Then", text, body).
Sourcepub fn note(&mut self, text: &str) -> &mut Self
pub fn note(&mut self, text: &str) -> &mut Self
Attach a text note to the current step or story.
Sourcepub fn tag(&mut self, names: &[&str]) -> &mut Self
pub fn tag(&mut self, names: &[&str]) -> &mut Self
Attach tag annotations to the current step or story.
Sourcepub fn kv(&mut self, label: &str, value: Value) -> &mut Self
pub fn kv(&mut self, label: &str, value: Value) -> &mut Self
Attach a key-value pair to the current step or story.
Sourcepub fn json(&mut self, label: &str, value: &impl Serialize) -> &mut Self
pub fn json(&mut self, label: &str, value: &impl Serialize) -> &mut Self
Attach a JSON document (serialized as code with lang=json) to the current step or story.
Sourcepub fn code(
&mut self,
label: &str,
content: &str,
lang: Option<&str>,
) -> &mut Self
pub fn code( &mut self, label: &str, content: &str, lang: Option<&str>, ) -> &mut Self
Attach a code block to the current step or story.
Sourcepub fn table(
&mut self,
label: &str,
columns: &[&str],
rows: &[&[&str]],
) -> &mut Self
pub fn table( &mut self, label: &str, columns: &[&str], rows: &[&[&str]], ) -> &mut Self
Attach a table to the current step or story.
Sourcepub fn link(&mut self, label: &str, url: &str) -> &mut Self
pub fn link(&mut self, label: &str, url: &str) -> &mut Self
Attach a link to the current step or story.
Sourcepub fn section(&mut self, title: &str, markdown: &str) -> &mut Self
pub fn section(&mut self, title: &str, markdown: &str) -> &mut Self
Attach a markdown section to the current step or story.
Sourcepub fn mermaid(&mut self, code: &str, title: Option<&str>) -> &mut Self
pub fn mermaid(&mut self, code: &str, title: Option<&str>) -> &mut Self
Attach a mermaid diagram to the current step or story.
Sourcepub fn screenshot(&mut self, path: &str, alt: Option<&str>) -> &mut Self
pub fn screenshot(&mut self, path: &str, alt: Option<&str>) -> &mut Self
Attach a screenshot reference to the current step or story.
Sourcepub fn html(&mut self, opts: HtmlOptions<'_>) -> &mut Self
pub fn html(&mut self, opts: HtmlOptions<'_>) -> &mut Self
Attach an embedded-HTML doc entry to the current step or story.
Exactly one of opts.path, opts.url, or opts.content must be set.
Sourcepub fn state(&mut self, label: Option<&str>, value: Value) -> &mut Self
pub fn state(&mut self, label: Option<&str>, value: Value) -> &mut Self
Attach a state snapshot to the current step or story.
value is a JSON-serializable snapshot of “what the world looks like”
at this step; consecutive states with the same label are diffed by the
report renderer. Pass None for an anonymous state lane (label omitted).
Sourcepub fn custom(&mut self, type_name: &str, data: Value) -> &mut Self
pub fn custom(&mut self, type_name: &str, data: Value) -> &mut Self
Attach a custom doc entry to the current step or story.
Sourcepub fn attach(&mut self, name: &str, media_type: &str, path: &str) -> &mut Self
pub fn attach(&mut self, name: &str, media_type: &str, path: &str) -> &mut Self
Attach a file-based attachment to the story.
Sourcepub fn attach_inline(
&mut self,
name: &str,
media_type: &str,
body: &str,
encoding: &str,
) -> &mut Self
pub fn attach_inline( &mut self, name: &str, media_type: &str, body: &str, encoding: &str, ) -> &mut Self
Attach an inline (body-based) attachment to the story.
Sourcepub fn attach_spans(&mut self, spans: Vec<Value>) -> &mut Self
pub fn attach_spans(&mut self, spans: Vec<Value>) -> &mut Self
Attach OTel spans for trace waterfall rendering in HTML reports.
Sourcepub fn pass(&mut self)
pub fn pass(&mut self)
Mark the story as passed.
Optional: a story dropped with no panic in flight already records
"pass". Kept so existing tests still compile, and so a Result-returning
test can state its happy path.
Sourcepub fn fail(&mut self)
pub fn fail(&mut self)
Mark the story as failed.
Needed only when a test fails without panicking, which in practice means
a #[test] returning Err. Panicking assertions are detected on drop.
Sourcepub fn record_result<T, E>(&mut self, result: Result<T, E>) -> Result<T, E>
pub fn record_result<T, E>(&mut self, result: Result<T, E>) -> Result<T, E>
Record the outcome of a Result, then hand it back for ? to bubble up.
§Errors
Returns result untouched, so the caller sees the original error.
fn parses_a_price() -> Result<(), std::num::ParseIntError> {
let mut story = Story::new("parses a price");
story.given("the string 499");
story.then("it parses to 499");
let parsed = story.record_result("499".parse::<u32>())?;
assert_eq!(parsed, 499);
Ok(())
}Sourcepub fn planned(scenario: &str)
pub fn planned(scenario: &str)
Record a scenario that is specified but not built yet. It appears in the report marked “planned” and stops being planned once someone writes it as a real story.
#[test]
fn checkout_blocks_suspended_account() {
Story::planned("checkout is blocked for a suspended account");
}#[ignore] means “do not run this now”, which is a different claim from
“we have not built this yet”, so this does not ignore the test for you.
The scenario is recorded when this returns, so keep it the only statement in the test: a panic afterwards cannot revise a record already written.