An implementation of the Cucumber testing framework for Rust. Fully native, no external test runners or dependencies.
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.2" }
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; } }
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
}
}
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
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.2", features = ["macros"] }
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};
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; } }
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),
})
}
}
async fn athing(world: &mut MyWorld) { world.foo = "elho".into(); world.testasync_fn().await; }
async fn somethinggoes(: &mut MyWorld, _wrong: String) {}
fn iamtryingout(world: &mut MyWorld) { world.foo = "Some string".tostring(); }
fn iconsider(world: &mut MyWorld) { let newstring = format!("{}.", &world.foo); world.foo = new_string; }
fn iaminterested(world: &mut MyWorld) { assert_eq!(world.foo, "Some string."); }
fn wecanregex(: &mut MyWorld, action: String) {
// action
can be anything implementing FromStr
.
asserteq!(action, "implement");
}
async fn main() { let runner = MyWorld::init(&["./features"]); runner.runandexit().await; } ```
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.
This project is licensed under either of
at your option.