cucumber-rust

Documentation Actions Status

An implementation of the Cucumber testing framework for Rust. Fully native, no external test runners or dependencies.

Usage

Create a directory called tests/ in your project root and create a test target of your choice. In this example we will name it cucumber.rs.

Add this to your Cargo.toml:

```toml [[test]] name = "cucumber" harness = false # Allows Cucumber to print output instead of libtest

[dev-dependencies] cucumber = { package = "cucumber_rust", version = "0.8.3" }

You can use any executor you want, but we're going to use Tokio in this example.

tokio = { version = "1", features = ["macros", "rt-multi-thread"] } ```

Create a directory called features/ and put a feature file in it named something like example.feature. It might look like:

```gherkin Feature: Example feature

Scenario: An example scenario Given I am trying out Cucumber When I consider what I am doing Then I am interested in ATDD And we can implement rules with regex

```

And here's an example of implementing those steps using our tests/cucumber.rs file:

```rust use cucumber::async_trait; use std::{convert::Infallible, cell::RefCell};

pub struct MyWorld { // You can use this struct for mutable context in scenarios. foo: String, bar: usize, some_value: RefCell, }

impl MyWorld { async fn testasyncfn(&mut self) { *self.somevalue.borrowmut() = 123u8; self.bar = 123; } }

[async_trait(?Send)]

impl cucumber::World for MyWorld { type Error = Infallible;

async fn new() -> Result<Self, Infallible> {
    Ok(Self {
        foo: "wat".into(),
        bar: 0,
        some_value: RefCell::new(0),
    })
}

}

mod example_steps { use cucumber::{Steps, t};

pub fn steps() -> Steps<crate::MyWorld> {
    let mut builder: Steps<crate::MyWorld> = Steps::new();

    builder
        .given_async(
            "a thing",
            t!(|mut world, _step| {
                world.foo = "elho".into();
                world.test_async_fn().await;
                world
            })
        )
        .when_regex_async(
            "something goes (.*)",
            t!(|world, _matches, _step| world),
        )
        .given(
            "I am trying out Cucumber",
            |mut world: crate::MyWorld, _step| {
                world.foo = "Some string".to_string();
                world
            },
        )
        .when("I consider what I am doing", |mut world, _step| {
            let new_string = format!("{}.", &world.foo);
            world.foo = new_string;
            world
        })
        .then("I am interested in ATDD", |world, _step| {
            assert_eq!(world.foo, "Some string.");
            world
        })
        .then_regex(
            r"^we can (.*) rules with regex$",
            |world, matches, _step| {
                // And access them as an array
                assert_eq!(matches[1], "implement");
                world
            },
        );

    builder
}

}

[tokio::main]

async fn main() { // Do any setup you need to do before running the Cucumber runner. // e.g. setupsomedb_thing()?;

cucumber::Cucumber::<MyWorld>::new()
    // Specifies where our feature files exist
    .features(&["./features"])
    // Adds the implementation of our steps to the runner
    .steps(example_steps::steps())
    // Parses the command line arguments if passed
    .cli()
    // Runs the Cucumber tests and then exists
    .run_and_exit()
    .await

} ```

You can then run your Cucumber tests by running this command:

cargo test --test cucumber

Auto-wiring via macros

By enabling macros feature in Cargo.toml: ```toml

[[test]] name = "cucumber" harness = false # Allows Cucumber to print output instead of libtest

[dev-dependencies] cucumber = { package = "cucumber_rust", version = "0.8.3", features = ["macros"] }

You can use any executor you want, but we're going to use Tokio in this example.

tokio = { version = "1", features = ["macros", "rt-multi-thread"] } ```

You could leverage some conveniences in organizing your tests code: ```rust use std::{cell::RefCell, convert::Infallible};

use cucumber::{async_trait, given, then, when, World, WorldInit};

[derive(WorldInit)]

pub struct MyWorld { // You can use this struct for mutable context in scenarios. foo: String, bar: usize, some_value: RefCell, }

impl MyWorld { async fn testasyncfn(&mut self) { *self.somevalue.borrowmut() = 123u8; self.bar = 123; } }

[async_trait(?Send)]

impl World for MyWorld { type Error = Infallible;

async fn new() -> Result<Self, Infallible> {
    Ok(Self {
        foo: "wat".into(),
        bar: 0,
        some_value: RefCell::new(0),
    })
}

}

[given("a thing")]

async fn athing(world: &mut MyWorld) { world.foo = "elho".into(); world.testasync_fn().await; }

[when(regex = "something goes (.*)")]

async fn somethinggoes(: &mut MyWorld, _wrong: String) {}

[given("I am trying out Cucumber")]

fn iamtryingout(world: &mut MyWorld) { world.foo = "Some string".tostring(); }

[when("I consider what I am doing")]

fn iconsider(world: &mut MyWorld) { let newstring = format!("{}.", &world.foo); world.foo = new_string; }

[then("I am interested in ATDD")]

fn iaminterested(world: &mut MyWorld) { assert_eq!(world.foo, "Some string."); }

[then(regex = r"^we can (.*) rules with regex$")]

fn wecanregex(: &mut MyWorld, action: String) { // action can be anything implementing FromStr. asserteq!(action, "implement"); }

[tokio::main]

async fn main() { let runner = MyWorld::init(&["./features"]); runner.runandexit().await; } ```

Supporting crates

The full gamut of Cucumber's Gherkin language is implemented by the gherkin-rust project. Most features of the Gherkin language are parsed already and accessible via the relevant structs.

License

This project is licensed under either of

at your option.