A simple, expressive unit test framework for Rust
In Cargo.toml:
toml
[dev-dependencies]
laboratory = "*"
Then in your test files
```rust
mod tests { use laboratory::{describe, describeskip, it, itskip, it_only, expect}; } ```
```rust // taken from examples/simple.rs fn main() { add_one(0); }
// Here we have one function that does // one thing: Adds one to whatever number // we pass to it. fn add_one (n: u64) -> u64 { n + 1 }
mod tests {
// lets pull our add_one function into scope
use super::*;
// now let's pull in our lab tools into scope
// to test our function
use laboratory::{describe, it, expect};
// From Rust's perspective we will only define
// one test, but inside this test we can define
// however many test we need.
#[test]
fn suite() {
// let's describe what our add_one function will do.
// Notice the method "specs" which takes a Vec as it's
// argument. Inside this vec is where we will define
// the tests related to add_one.
describe("add_one()").specs(vec![
// when describing what it should do, feel free to be
// as expressive as you would like.
it("should return 1 when passed 0", |_| {
// here we will use the default expect function
// that comes with laboratory.
// We expect the result of add_one(0) to equal 1
expect(add_one(0)).to_equal(1)
}),
// just as a sanity check, let's add a second test
it("should return 2 when passed 1", |_| {
expect(add_one(1)).to_equal(2)
})
]).run();
}
} ```
Then run:
shell script
$ cargo test -- --nocapture
Result:
```
running 1 test
add_one() ✓ should return 1 (0ms) ✓ should return 2 (0ms)
✓ 2 tests completed (0ms)
test tests::suite ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ```
```rust // taken from examples/nested-suites.rs
// Here we will define a struct with // two members, an associated function, // and two methods. struct Foo { line: String, count: i32 } impl Foo { pub fn new() -> Foo { Foo { line: String::new(), count: 0 } } pub fn append(&mut self, str: &str) { self.line += str; } pub fn increase(&mut self) { self.count += 1; } }
fn main () { let mut foo = Foo::new(); foo.append("fizzbuzz"); foo.increase(); }
mod tests {
// Pull the Foo struct into scope
use super::*;
// Now pull in the lab tools
use laboratory::{describe, it, expect};
// define single test
#[test]
fn test() {
// Now we can describe Foo.
// Notice the "suites" method that takes a Vec
// as its argument. This is where we can describe
// Foo's members and methods.
describe("Foo").suites(vec![
// Here we describe the "new" associated function
describe("#new()").specs(vec![
it("should return an instance of Foo with two members", |_| {
let result = Foo::new();
expect(result.bar).to_be(String::new())?;
expect(result.baz).to_equal(0)
})
]),
// Now we will describe the "append" method
describe("#append()").specs(vec![
it("should append fizzbuzz to Foo#bar", |_| {
let mut foo = Foo::new();
foo.append("fizzbuzz");
expect(foo.bar).to_be("fizzbuzz".to_string())
})
]),
// Finally, we will describe the "increase" method
describe("#increase()").specs(vec![
it("should increase Foo#baz by 1", |_| {
let mut foo = Foo::new();
foo.increase();
expect(foo.baz).to_equal(1)
})
])
])
.run();
}
}
Result:
textmate
running 1 test
Foo #new() ✓ should return foo with two members (0ms) #append() ✓ should append fizzbuzz to Foo#bar (0ms) #increase() ✓ should increase Foo#baz by 1 (0ms)
✓ 3 tests completed (0ms)
test tests::test ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ```
```rust // from examples/failure.rs
// addone should add one to any number, // but a typo results in 6 being added fn addone (n: i32) -> i32 { n + 6 }
fn add_two (n: i32) -> i32 { n + 2 }
fn main() { addone(0); addtwo(0); }
mod tests {
// let call in our functions
use super::*;
// and now let's bring in our lab tools
use laboratory::{describe,it,expect};
// define our single rust test
#[test]
fn test() {
// We have two different functions that we
// want to test in our crate. So, let's
// describe our crate and nest our functions
// under that umbrella.
describe("Crate").suites(vec![
describe("add_one()").specs(vec![
it("should return 1 to when passed 0", |_| {
expect(add_one(0)).to_equal(1)
})
]),
describe("add_two()").specs(vec![
it("should return 2 to when passed 0", |_| {
expect(add_two(0)).to_equal(2)
})
])
]).run();
}
}
```
Result: ```textmate
running 1 test
Package addone() 0) should return 1 to when given 0 (0ms) addtwo() ✓ should return 2 to when given 0 (0ms)
✖ 1 of 2 tests failed:
0) add_one() should return 1 to when given 0: Expected 6 to equal 1
test tests::test ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
```rust
fn no_op() -> bool { true }
fn main() { no_op(); }
mod tests {
use super::no_op;
use laboratory::{describe, it, expect};
#[test]
fn test() {
describe("no_op").before_all(|_| {
println!("\n\n before hook called");
}).before_each(|_| {
println!(" before_each hook called");
}).after_each(|_| {
println!(" after_each hook called");
}).after_all(|_| {
println!(" after_all hook called");
}).specs(vec![
it("should do nothing", |_| {
expect(no_op()).to_be(true)
}),
it("should do nothing again", |_| {
expect(no_op()).to_be(true)
})
]).run();
}
}
Result:
running 1 test
before hook called beforeeach hook called aftereach hook called beforeeach hook called aftereach hook called after_all hook called
no_op ✓ should do nothing (0ms) ✓ should do nothing again (0ms)
✓ 2 tests completed (0ms)
test tests::test ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ```
```rust
fn noop() -> bool { true } fn addone(n: i32) -> i32 { n + 1 } fn add_two(n: i32) -> i32 { n + 2 }
fn main() { noop(); addone(0); }
mod tests {
use super::*;
use laboratory::{describe, it, expect, Deserialize, Serialize, State};
use std::fmt::{Debug};
use crate::add_one;
// We want a counter to count each time a hook or test is called
// Any state we want to use in the suite must be able to be serialized and deserialized by serde
#[derive(Deserialize, Serialize, Debug)]
struct Counter {
// the counter will hold a member for each category
pub before_all_hit_count: u8,
pub before_each_hit_count: u8,
pub after_each_hit_count: u8,
pub after_all_hit_count: u8,
pub test_hit_count: u8
}
impl Counter {
fn new() -> Counter {
Counter {
before_all_hit_count: 0,
before_each_hit_count: 0,
after_each_hit_count: 0,
after_all_hit_count: 0,
test_hit_count: 0
}
}
}
#[test]
fn test() {
fn update_before_all(state: &mut State) {
let mut counter: Counter = state.get_state();
counter.before_all_hit_count += 1;
state.set_state(counter);
}
fn update_before_each(state: &mut State) {
let mut counter: Counter = state.get_state();
counter.before_each_hit_count += 1;
state.set_state(counter);
}
fn update_after_each(state: &mut State) {
let mut counter: Counter = state.get_state();
counter.after_each_hit_count += 1;
state.set_state(counter);
}
fn update_after_all(state: &mut State) {
let mut counter: Counter = state.get_state();
counter.after_all_hit_count += 1;
// println!("\n\n{:#?}", &counter);
state.set_state(counter);
}
fn update_test(state: &mut State) {
let mut counter: Counter = state.get_state();
counter.test_hit_count += 1;
state.set_state(counter);
}
let state: Counter = describe("no_op")
.state(Counter::new())
.before_all(update_before_all)
.before_each(update_before_each)
.after_each(update_after_each)
.after_all(update_after_all)
.suites(vec![
// this suite will inherit the parent suite's state
describe("add_one()")
.before_all(update_before_all)
.before_each(update_before_each)
.after_each(update_after_each)
.after_all(update_after_all)
.specs(vec![
it("should return 1", |state| {
update_test(state);
expect(add_one(0)).to_be(1)
}),
it("should return 2", |state| {
update_test(state);
expect(add_one(1)).to_be(2)
})
])
.inherit_state(),
// it will use its own state
describe("add_two()")
.state(Counter::new())
.before_all(update_before_all)
.before_each(update_before_each)
.after_each(update_after_each)
.after_all(update_after_all)
.specs(vec![
it("should return 2", |state| {
update_test(state);
expect(add_two(0)).to_be(2)
}),
it("should return 4", |state| {
update_test(state);
expect(add_two(2)).to_be(4)
})
])
])
.specs(vec![
it("should do nothing", |state| {
update_test(state);
expect(no_op()).to_be(true)
}),
it("should do nothing again", |state| {
update_test(state);
expect(no_op()).to_be(true)
})
]).run().to_state();
println!("{:#?}\n\n", state);
}
}
Result:
running 1 test
noop ✓ should do nothing (0ms) ✓ should do nothing again (0ms) addone() ✓ should return 1 (0ms) ✓ should return 2 (0ms) add_two() ✓ should return 2 (0ms) ✓ should return 4 (0ms)
✓ 6 tests completed (0ms)
Counter { beforeallhitcount: 2, beforeeachhitcount: 4, aftereachhitcount: 4, afterallhitcount: 2, testhitcount: 4, }
test tests::test ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ```
```rust
fn main() { addone::addone(0); multiplybytwo::multiplybytwo(1); }
pub mod add_one {
pub fn add_one (x: u64) -> u64 { x + 1 }
#[cfg(test)]
pub mod tests {
use super::*;
use laboratory::{describe, it, expect, Suite};
pub fn suite() -> Suite {
describe("add_one()").specs(vec![
it("should return 1", |_| {
expect(add_one(0)).to_equal(1)
}),
it("should return 2", |_| {
expect(add_one(1)).to_equal(2)
})
])
}
}
} pub mod multiplybytwo {
pub fn multiply_by_two (x: u64) -> u64 { x * 2 }
#[cfg(test)]
pub mod tests {
use super::*;
use laboratory::{describe, it, expect, Suite};
pub fn suite() -> Suite {
describe("multiply_by_two()").specs(vec![
it("should return 2", |_| {
expect(multiply_by_two(1)).to_equal(2)
}),
it("should return 4", |_| {
expect(multiply_by_two(2)).to_equal(4)
})
])
}
}
}
mod tests { use super::*; use laboratory::{describe};
#[test]
fn test() {
describe("Package").suites(vec![
add_one::tests::suite(),
multiply_by_two::tests::suite()
]).run();
}
}
Result:
running 1 test
Package addone() ✓ should return 1 (0ms) ✓ should return 2 (0ms) multiplyby_two() ✓ should return 2 (0ms) ✓ should return 4 (0ms)
✓ 4 tests completed (0ms)
test tests::test ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ```
```rust fn main() { addone(0); addtwo(0); }
fn addone (x: u64) -> u64 { x + 1 } fn addtwo (x: u64) -> u64 { x + 5 }
mod tests {
use super::*;
use laboratory::{describe, it, expect};
#[test]
fn suite() {
describe("Package").suites(vec![
describe("add_one()").specs(vec![
it("should return 1", |_| {
expect(add_one(0)).to_equal(1)
}),
it("should return 2", |_| {
expect(add_one(1)).to_equal(2)
})
]),
describe("add_two()").specs(vec![
it("should return 2", |_| {
expect(add_two(0)).to_equal(2)
})
])
]).json_pretty().run();
}
}
Result:
running 1 test
{ "name": "Package", "passing": 2, "failing": 1, "ignored": 0, "childsuites": [ { "name": "addone()", "passing": 2, "failing": 0, "ignored": 0, "childsuites": [], "childtests": [ { "name": "should return 1", "fullname": "addone() should return 1", "pass": true, "errormsg": null, "duration": 0 }, { "name": "should return 2", "fullname": "addone() should return 2", "pass": true, "errormsg": null, "duration": 0 } ], "duration": 0 }, { "name": "addtwo()", "passing": 0, "failing": 1, "ignored": 0, "childsuites": [], "childtests": [ { "name": "should return 2", "fullname": "addtwo() should return 2", "pass": false, "errormsg": "Expected 5 to equal 2", "duration": 0 } ], "duration": 0 } ], "child_tests": [], "duration": 0 }
test tests::suite ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ```