Rust BDD tests with Cucumber
Tags: rust, cucumber, testing, beginners
Published: 2021-08-04 14:31:01

> **This post is out of date.** The `cucumber` crate version used here is obsolete. Fortunately, the new version is not only better, but also easier to use and has excellent documentation, which you can find [here](https://cucumber-rs.github.io/cucumber/current/).

**Cucumber** is a tool for _behavior-driven development_ (BDD) that uses a language called **Gherkin** to specify test scenarios with a syntax that is very close to natural language, using key-words such as `When` and `Then`.

Here I will not explain the particularities of neither Cucumber nor Gherkin. My goal is to just show you how to use them with Rust. If you know nothing about them, I recommend you to:
- Watch this [soft introduction](https://www.youtube.com/watch?v=lC0jzd8sGIA).
- Refer to the [documentation](https://cucumber.io/docs/cucumber/), specially the [Gherkin reference guide](https://cucumber.io/docs/gherkin/reference/). 

That being said, the examples I am using are elementary, so you should have no problem following along even if you have never seen Cucumber and Gherkin before.

---

## Sample project
> The code used in this tutorial can be found [here](https://github.com/rogertorres/dev.to/tree/main/cucumber).

To focus on how to use Cucumber with Rust, I decided to code a dummy multiplication function, so you don't get distracted by the idiosyncrasies of a particular project.

First, create a library crate:
```
$ cargo new --lib bdd
```

Then, remove the mod `tests` from `lib.rs` and code this:

```rust
pub fn mult(a: i32, b: i32) -> i32 {
    a * b
}
```

And that's all for `lib.rs`.

---

## Setting up the manifest
The manifest (`Cargo.toml`) require these entries:

```toml
[[test]]
name = "cucumber"
harness = false

[dependencies]
tokio = { version = "1.9.0", features = ["rt-multi-thread", "macros", "time"] }


[dev-dependencies]
cucumber_rust = "0.9"
```

In `[[test]]` we have:
- `name`, which is the name of the `.rs` file where we will code the tests, `cucumber.rs` in this case.
- `harness` is set to false, allowing us to provide our own `main` function to handle the test run. This `main` function will be placed inside the `.rs` file specified above.

In `[dependencies]` we have `tokio`, which is an _async_ runtime required to work with cucumber-rust (even if you are not testing anything _async_). You may use another runtime.

And in `[dev-dependencies]` we have `cucumber_rust`, the star of this show.

---

## Project structure
We have to create a couple of files:
- `tests/cucumber.rs`, where we will have the `main` function that will run the tests.
- `features/operations.feature`, where we will code our test scenario using Gherkin.

At the end, we have this:

```text
bdd
├── features
│   └── operations.feature
├── src
│   └── lib.rs
├── tests
│   └── cucumber.rs
└── Cargo.toml
```

---

## Coding the test with Gherkin
This is how the `operation.feature` file looks like:

```gherkin
Feature: Arithmetic operations

  # Let's start with addition. BTW, this is a comment.
  Scenario: User wants to multiply two numbers
    Given the numbers "2" and "3"
    When the User adds them
    Then the User gets 6 as result
```

Here we have a context set by `Given`, an event described by `When` and an expected result expressed by `Then`.

Although my purpose is not to teach Cucumber, I have to say that this is **not** a good BDD scenario. Not so much because it is dead simple, but because it is a unit test in disguise. I have, nevertheless, kept it here because it has the virtue of making things crystal clear regarding how we are going to interpret this scenario with Rust. For best practices, check [this](https://support.smartbear.com/cucumberstudio/docs/tests/best-practices.html#testing-process-write-test-scenarios-at-early-st).

---

## Handle the scenario with Rust
From now on, we stick with [~~the mighty Crabulon~~](https://twitter.com/lunasorcery/status/1402372190033563650) Rust.

### World object
The first thing we need is a `World`, an object that will hold the state of our test during execution.

Let's start coding `cucumber.rs`:

```rust
use cucumber_rust::{async_trait, Cucumber, World};
use std::convert::Infallible;

pub enum MyWorld {
    Init,
    Input(i32, i32),
    Result(i32),
    Error,
}

#[async_trait(?Send)]
impl World for MyWorld {
    type Error = Infallible;

    async fn new() -> Result<Self, Infallible> {
        Ok(Self::Init)
    }
}
```

For now, we created an `enum` named `MyWorld` that will be our "World object", holding the data between steps. `Init` is its initial value.

The `MyWorld` object implements the trait `World` provided by the `cucumber_rust`, which in turn gives us the methods to map the steps (`Given`, `When`, `Then`, etc.). 

The `Error` type is a requirement from this trait, as is the attribute `#[async_trait(?Send)]`.

### Step builder
Now it is time to actually code the interpreter. I will explain the code block by block, so it might be useful to also have the [complete code](https://github.com/rogertorres/dev.to/blob/main/cucumber/tests/cucumber.rs) open, so you don't lose sight of the whole picture.

#### Given
```rust
mod test_steps {
    use crate::MyWorld;
    use cucumber_rust::Steps;
    use bdd::mult;

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

        builder.given_regex(
            // This will match the "given" of multiplication
            r#"^the numbers "(\d)" and "(\d)"$"#,
            // and store the values inside context, 
            // which is a Vec<String>
            |_world, context| {
                // With regex we start from [1]
                let world = MyWorld::Input(
                    context.matches[1].parse::<i32>().unwrap(),
                    context.matches[2].parse::<i32>().unwrap(),
                );
                world
            }
        );

        // The rest of the code will go here.
    }
}
```

After declaring the `mod`, we created our `builder`, a `Steps` struct that will store our steps.

The crate `cucumber_rust` provides three variations for the main Gherkin prefixes (`Given`, `When`, `Then`):
- The "normal" one that matches fixed values (e.g. `when()`);
- The _regex_ version that parses the _regex_ input (e.g. `when_regex()`).
- The _async_ version to handle _async_ tests, something I am not covering here (e.g. `when_async()`).
- The _async_+_regex_, which is a combination of the last two (e.g. `when_regex_async()`), also not covered here.

I am using `given_regex()` to parse the two numbers. Remember that in `operations.feature` I specified this:
```gherkin
Given the numbers "2" and "3"
```

When you call a step function such as `given_regex()` you get a [closure](https://doc.rust-lang.org/rust-by-example/fn/closures.html) containing the `World` object and a `Context`. The latter have a field called `matches` that is a `Vec<String>` containing the _regex_ matches (if you're not using a `_regex` step, the Vector will be empty). In this case, as I am using _regex_, it has three values: 
- [0] has the entire match, `the numbers "2" and "3"` in this case.
- [1] has the first group, `2` in this case.
- [2] has the first group, `3` in this case.

> This is the _regex_ "normal" behavior. If you are not familiar with _regex_, [this is a good intro](https://www.youtube.com/watch?v=sa-TUpSx1JA) (thank you YouTube for holding my _watch history_ for so long).

With these values, I return my `World` object now set as `Input`.

Before we move to `when`, I have two quick remarks to make:
- I am not checking the `unrwap()` because the _regex_ is only catching numbers with `(\d)`. Sometimes you might want to capture everything with something like `(.*)` and validate the content inside your code.
- If you want to change your `World` object (for example, if it is a struct holding multiple values and/or states), just place `mut` before `world` in the closure, and you will get a mutable object.

#### When
```rust
builder.when(
    "the User multiply them", 
    |world, _context|{
        match world {
            MyWorld::Input(l, r) => MyWorld::Result(mult(l,r)),
            _ => MyWorld::Error,
        }
    }
);
```

This one is very straightforward. I use `match` to get the `enum` inner value, multiply both inputs and return the World object with a new value.

The function `mult` is ratter useless, but it has a role to play here: to show you how to import what we declared within the library crate.

#### Then
```rust

builder.then_regex(
    r#"^the User gets "(\d)" as result$"#, 
    |world, context|{
        match world {
            MyWorld::Result(x) => assert_eq!(x.to_string(), context.matches[1]),
            _ => panic!("Invalid world state"),
        };
        MyWorld::Init
    }
);

builder
```

Here I use _regex_ again to compare the value that was calculated in the `Then` step with the value provided by Gherkin (which is, as I said, a very suspicious BDD scenario). 

At the very end, I return `builder`.

---

## The substitute `main` function
After the `mod`, we declare our `main` function:
```rust
#[tokio::main]
async fn main() {
    Cucumber::<MyWorld>::new()
        .features(&["./features"])
        .steps(test_steps::steps())
        .run_and_exit()
        .await
}
```

Here we are:
1. Creating a `World` object
2. Pointing to the `.feature` file containing the Gherkin test.
3. Adding the steps defined with `cucumber_rust`.
4. Running and exiting once it is done.
5. The `await` because `cucumber_rust` requires the whole thing to be _async_.

That's it! To test it, all you have to do is to run
```
$ cargo test
```

Rust will run the `main` function found in the file specified in the manifest: `cucumber.rs`. This is the result.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6euyxd80lhvblcfyqwj.png)

I recommend you to mess with the values and run the tests, so you can see how the errors are captured.

---

Much more can be done with `cucumber_rust`. I hope this tutorial helps you get started with it.

> Cover image by _[Roman Fox](https://unsplash.com/photos/4jqhNG5SDtU)_.