#bdd #spec #cargo-test #test-files

spectest

A lightweight library for defining behavior-driven development (BDD) style tests in exernal files and running them with cargo test

3 releases

0.1.2 Jul 23, 2024
0.1.1 Jun 26, 2024
0.1.0 Jun 24, 2024

#492 in Testing

MIT/Apache

60KB
1.5K SLoC

Spectest - lightweight BDD testing for Rust

A lightweight library for defining behavior-driven development (BDD) style tests in exernal files and running them with cargo test. This can be seen as a successor / alternative of the datadriven crate.

Please consult the spectest docs and the integration tests for details.


lib.rs:

A lightweight library for defining behavior-driven development (BDD) style tests in external files and running them with cargo test.

To write a test:

  1. Implement a Handler that interprets Background and Example sections defined in your spec file.
  2. Write a test that calls [run] with a Handler instance and a path that points to a spec file. You can also use glob_test to derive one such test for each spec file in a given folder (including subfolders).

Example

Here is a minimal example:

use spectest;

struct MevalHandler<'a> {
    ctx: meval::Context<'a>,
}

impl<'a> MevalHandler<'a> {
    fn new() -> Self {
        Self {
            ctx: meval::Context::new(),
        }
    }
}

impl<'a> spectest::Handler for MevalHandler<'a> {
    type Error = String;

    fn example(&mut self, example: &mut spectest::Example) -> Result<(), Self::Error> {
        let Some(input) = example.when.get("input") else {
            let msg = format!("missing `input` definition in the 'When' spec");
            return Err(msg);
        };
        let input = match input.parse::<meval::Expr>() {
            Ok(expr) => expr,
            Err(err) => {
                let msg = format!("cannot parse `input` expression `{input}`: {err}");
                return Err(msg);
            }
        };

        match input.eval_with_context(self.ctx.clone()) {
            Ok(value) => {
                example.then.insert("result", value.to_string() + "\n");
            }
            Err(err) => {
                let msg = format!("cannot evaluate expression: {err}\n");
                example.then.insert("result", msg);
            }
        }

        Ok(())
    }
}

#[spectest::glob_test("testdata/integration/**/*.md")]
fn test_meval(path: &str) {
    let mut handler = MevalHandler::new();
    spectest::run(path, &mut handler);
}

Assuming that the testdata/integration folder contains a single called calculator.md, one can run the test against this file as follows:

# Expand the prefix to narrow the set of tested spec files
cargo test test_meval_

It is also possible to mass-rewrite failing tests after fixing/updating the behavior of the meval library under test as follows

REWRITE_SPECS=true cargo test test_calculator

For a more elaborated version that also updates the evaluation context depending on the currently active Background sections, see the test/integration.rs in the source repository.

Dependencies

~3–4.5MB
~76K SLoC