Skip to content

Guides · 1 of 7

Write feature files

A feature file is the part of the suite everyone reads. Scenarios, Background, Outlines, tables, docstrings, tags, and other languages, with the Rust each one needs.

One scenario

A file starts with Feature: and holds one or more scenarios. Each scenario is a list of steps, and each step starts with a keyword: Given for the starting state, When for the action, Then for the result. And and But continue whichever keyword came before.

tests/features/lantern.feature
Feature: Lantern delivery
  Scenario: Deliver a lantern
    Given a lantern on the trolley
    When the departure bell rings
    Then the lantern arrives upright

Compiled and run against rstest-bdd 0.6.0.

Write steps about what someone could observe, not about the code that makes it happen. “The lantern arrives upright” survives a rewrite of the trolley; “upright is true” does not.

Background, tables, and docstrings

A Background runs before every scenario in the file. A data table follows a step as rows between pipes; a docstring follows a step between triple quotes. Both belong to the step above them.

tests/features/hamper.feature
Feature: Packing the hamper
  Background:
    Given an empty hamper

  Scenario: Pack the picnic
    When the following items are packed:
      | item       | quantity | fragile |
      | shortbread | 12       | no      |
      | teapot     | 1        | yes     |
      | lantern    | 2        | yes     |
    Then the hamper holds 15 items
    And 2 kinds of item need careful handling

  Scenario: Leave a note on top
    When a note is tucked under the lid:
      """
      Back by dusk.
      Save Marrow a biscuit.
      """
    Then the note mentions "biscuit"

Compiled and run against rstest-bdd 0.6.0.

A step receives its table through a #[datatable] parameter and its docstring through a parameter named docstring. Rows<Item> parses each row into a struct that derives DataTableRow, matching columns to fields by the header; #[datatable(truthy)] reads “yes” and “no” as booleans.

tests/hamper.rs
//! A `DataTableRow` struct binding a Gherkin data table, and a step that
//! captures a doc string as a plain `String` argument.

use rstest::fixture;
use rstest_bdd::datatable::Rows;
use rstest_bdd_macros::{DataTableRow, given, scenario, then, when};

#[derive(Debug, DataTableRow)]
struct Item {
    item: String,
    quantity: u32,
    #[datatable(truthy)]
    fragile: bool,
}

#[derive(Default)]
struct Hamper {
    items: Vec<Item>,
    note: String,
}

#[fixture]
fn hamper() -> Hamper {
    Hamper::default()
}

#[given("an empty hamper")]
fn empty(hamper: &mut Hamper) {
    hamper.items.clear();
}

#[when("the following items are packed:")]
fn pack(hamper: &mut Hamper, #[datatable] rows: Rows<Item>) {
    hamper.items.extend(rows);
}

#[then("the hamper holds {total:u32} items")]
fn holds(hamper: &Hamper, total: u32) {
    let packed: u32 = hamper.items.iter().map(|row| row.quantity).sum();
    assert_eq!(packed, total);
}

#[then("{kinds:usize} kinds of item need careful handling")]
fn fragile(hamper: &Hamper, kinds: usize) {
    let careful: Vec<&str> = hamper
        .items
        .iter()
        .filter(|row| row.fragile)
        .map(|row| row.item.as_str())
        .collect();
    assert_eq!(careful.len(), kinds, "fragile: {careful:?}");
}

#[when("a note is tucked under the lid:")]
fn tuck(hamper: &mut Hamper, docstring: String) {
    hamper.note = docstring;
}

#[then("the note mentions \"{word}\"")]
fn mentions(hamper: &Hamper, word: String) {
    assert!(hamper.note.contains(&word), "note: {:?}", hamper.note);
}

#[scenario(path = "tests/features/hamper.feature", name = "Pack the picnic")]
fn pack_the_picnic(hamper: Hamper) {
    let _ = hamper;
}

#[scenario(path = "tests/features/hamper.feature", name = "Leave a note on top")]
fn leave_a_note(hamper: Hamper) {
    let _ = hamper;
}

Compiled and run against rstest-bdd 0.6.0.

Scenario Outlines

An Outline is a scenario with placeholders in angle brackets and an Examples table. Each row becomes a test case, and each <placeholder> is replaced by that row’s value before any step is matched, so the steps see a trolley carrying 2 lanterns.

tests/features/counting.feature
Feature: Counting lanterns
  Scenario Outline: Deliver <count> lanterns
    Given a trolley carrying <count> lanterns
    When the departure bell rings
    Then <count> lanterns arrive at the picnic

    Examples:
      | count |
      | 1     |
      | 2     |
      | 0     |

Compiled and run against rstest-bdd 0.6.0.

The rows run as counting_lanterns::case_1, case_2, and case_3. The Rust is on the getting started page.

Tags

A tag is a word starting with @, on the line above a feature, a scenario, or an Examples table. Tags select which scenarios become tests, and @allow_skipped marks a skip as expected.

tests/features/auto/weather.feature
Feature: Picnic weather
  @smoke
  Scenario: A dry evening
    Given the forecast is "dry"
    Then the blanket goes on the grass

  @wip
  Scenario: Fog over the loch
    Given the forecast is "fog"
    Then the lanterns are lit early

  @smoke @allow_skipped
  Scenario: Thunder in the hills
    Given the forecast is "thunder"
    Then the blanket goes on the grass

Compiled and run against rstest-bdd 0.6.0.

Selection happens when the test compiles, so a scenario a tag expression leaves out is not a test at all rather than an ignored one. The expressions are in bind and filter scenarios.

Other languages

A # language: line at the top of the file switches the keywords to any language Gherkin knows. Nothing else needs configuring; the step patterns are written in the same language as the steps.

tests/features/pique_nique.feature
# language: fr
Fonctionnalité: Le pique-nique aux lanternes
  Scénario: Livrer une lanterne
    Soit une lanterne sur le chariot
    Quand la cloche du départ sonne
    Alors la lanterne arrive debout

Compiled and run against rstest-bdd 0.6.0.

tests/pique_nique.rs
//! A scenario written in French, exercising the `# language: fr` Gherkin
//! header and its Soit/Quand/Alors keywords.

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

#[derive(Default)]
struct Chariot {
    debout: bool,
    arrive: bool,
}

#[fixture]
fn chariot() -> Chariot {
    Chariot::default()
}

#[given("une lanterne sur le chariot")]
fn charger(chariot: &mut Chariot) {
    chariot.debout = true;
}

#[when("la cloche du départ sonne")]
fn partir(chariot: &mut Chariot) {
    chariot.arrive = true;
}

#[then("la lanterne arrive debout")]
fn verifier(chariot: &Chariot) {
    assert!(chariot.arrive && chariot.debout);
}

#[scenario(path = "tests/features/pique_nique.feature")]
fn livrer_une_lanterne(chariot: Chariot) {
    let _ = chariot;
}

Compiled and run against rstest-bdd 0.6.0.

The repository’s japanese-ledger example does the same in Japanese, with a 背景 (Background) and typed placeholders.

Fine print