Crates.io | rassert |
lib.rs | rassert |
version | 1.3.0 |
source | src |
created_at | 2021-09-30 08:24:49.402154 |
updated_at | 2021-10-02 17:08:27.445076 |
description | Fluent, easy-to-extend test assertion library. |
homepage | https://github.com/battila7/rassert |
repository | https://github.com/battila7/rassert |
max_upload_size | |
id | 458608 |
size | 34,315 |
expect_matches!
macro to check if an expression matches some pattern, such as expect_matches!(result, Ok(..))
.conclude_panic
or conclude_result
function call. In soft mode (turn on via the soft()
call), xpectation chain evaluation does not stop on the first failure. See Writing Assertions.First, add rassert to the dev-dependencies
section of your Cargo.toml
file:
[dev-dependencies]
rassert = "1"
Then simply import the prelude
module in your tests:
#[cfg(test)]
mod tests {
use rassert::prelude::*;
#[test]
fn rassert_works() {
expect!(&true)
.not()
.to_be_false()
.and()
.to_equal(true)
.conclude_panic();
}
}
In rassert one can write assertions in the form of expectation chains. Such chains allow for writing multiple expectations against the same expression. There are two so-called entry points with which one can start a chain:
expect!(expression)
expression
.expect_matches!(expression, pattern)
expression
and automatically adds an expectation to the chain, asserting that expression
matches pattern
.Once a chain is started, one can subsequently call expectations on it, as follows:
let v = vec![10];
expect!(&v)
.to_be_non_empty()
.and()
.to_contain(10);
Note, that the and()
call is not mandatory, as it only serves readability purposes.
Since rassert evaluates expectations lazily, a chain like the above one will do nothing. A chain will only assert the specified expectations when concluded:
// Will panic on a failed expectation.
expect!(&true)
.to_be(false)
.conclude_panic();
// Will return Result<(), String> containing the error
// message on failure.
let res = expect!(&true)
.to_be(false)
.conclude_result();
A chain can be put into soft mode by calling soft()
prior to concluding the chain:
let v = vec![10];
expect!(&v)
.to_contain(15)
.and()
.to_contain(20)
.soft()
.conclude_panic();
Soft chains will not panic/return on the first failure, instead they will run each assertion and present a merged report of every failure that occurred.
One can negate a single subsequent expectation using the not()
function:
expect!(&true)
.not()
.to_be_false()
.conclude_panic();
If one wishes to negate additionals expectations, then not()
has to be applied again.
T
to
T
where T: Debug + PartialEq
to_equal
, to_be
to_not_equal
, to_not_be
boolean
to_be_true
to_be_false
Option<T>
to_be_some
to_be_none
Option<T>
where T: Debug + PartialEq
to_contain
Result<T, E>
to_be_ok
to_be_err
˙Result<T, E>
where T: Debug + PartialEq
to_be_ok_with
Vec<T>
to_have_length
to_be_empty
to_be_non_empty
Vec<T>
where T: Debug + PartialEq
to_contain
Custom expectations can be written as extension traits on the ExpectationChain
type, provided by rassert. In what follows, we show how to write custom expectations through an example.
Let's assume, that we want to write an expectation against a custom struct, Pizza
:
#[derive(Debug)]
pub struct Pizza {
pub flavor: String
}
In rassert, expectations are actually structs, following something like a Command pattern. Given, we want to check what flavor of Pizza we have, we can create something as follows:
use rassert::Expectation;
struct ExpectPizzaFlavor {
expected_flavor: String
}
impl Expectation<Pizza> for ExpectPizzaFlavor {
fn test(&self, actual: &Pizza) -> bool {
actual.flavor.eq(&self.expected_flavor)
}
fn message(&self, expression: &str, actual: &Pizza) -> String {
format!("Expected {:?}\n to have flavor {}\n but had {}.", expression, self.expected_flavor, actual.flavor)
}
}
Implementing the Expectation
trait comes with two functions:
fn(&self, actual) -> bool
actual
parameter corresponds to the value being tested in the chain.fn(&self, expression, actual)
-> String
expression
parameter is the stringified expression argument of the expect!
/expect_matches!
macro.Once we got our expectation struct written, we can finally extend the ExpectationChain
type:
use rassert::ExpectationChain;
pub trait PizzaExpectationsExt<'a> {
fn to_have_flavor(self, expected: &str) -> ExpectationChain<'a, Pizza>;
}
impl<'a> PizzaExpectationsExt for ExpectationChain<'a, Pizza> {
fn to_have_flavor(self, expected: &str) -> ExpectationChain<'a, Pizza> {
self.expecting(ExpectPizzaFlavor {
expected_flavor: expected.to_owned()
})
}
}
The most important bits of the above snippet are the following:
ExpectationChain
type returned from expectation functions. This parameter corresponds to the lifetime of the immutable reference held inside the chain. This refernece then refers to the actual tested value.self
since expectation chains are Consuming builders.expecting()
function. This function takes an Expectation which will be executed when concluding the chain. The fields of the expectation can be used to parameterize the actual assertion.Then, using the above expectation is as easy as
let pizza = Pizza {
flavor: "Hawaii".to_owned(),
};
expect!(&pizza)
.to_have_flavor("Margherita")
.conclude_panic();
The built-in expectations of the src/expectations directory also use the above facilities, therefore they serve as a great starting point for writing custom expectations.
Licensed under The MIT License