rassert 1.1.0

Fluent, easy-to-extend test assertion library.
Documentation
<h1 align="center">
  rassert
</h1>

<h3 align="center">
  :crab: :question: :exclamation:
<h3>

<h3 align="center">
  Fluent, easy-to-extend testing assertions for Rust.
</h3>

<p align="center">
  <a href="https://github.com/battila7/rassert/blob/master/LICENSE">
    <img src="https://img.shields.io/github/license/battila7/rassert" alt="rassert uses The MIT License.">
  </a>
  <a href="https://www.npmjs.com/package/mvnx">
    <img src="https://img.shields.io/crates/v/rassert" alt="Current crates.io version.">
  </a>
  <a href="https://github.com/battila7/rassert/actions/workflows/continuous-integration.yml">
    <img src="https://github.com/battila7/rassert/actions/workflows/continuous-integration.yml/badge.svg" alt="Continuous Integration status.">
  </a>
</p>

## Features


  * **Fluent Expectations.** When writing tests, we strive for clarity and readability. rassert supports this goal by providing a fluent expectation interface allowing one to write assertions as close to natural language as possible. See [Writing Assertions]#writing-assertions.
  * **Human-friendly Failures.** Instead of cryptic failure messages, rassert displays the tested expression, its location, the actual value and the expected value/condition on failure to aid debugging.
  * **Pattern Matching Support.** Use the `expect_matches!` macro to check if an expression matches some pattern, such as `expect_matches!(result, Ok(..))`.
  * **Lazy and Soft Evaluation.**  Assertions are evaluated lazily, only when closing an expectation chain with a `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]#writing-assertions.
  * **Easy-to-Extend.** Extend rassert with custom expectations easily and succinctly. See [Custom Expectations]#custom-expectations.

## Up and Running


First, add rassert to the `dev-dependencies` section of your `Cargo.toml` file:

~~~~TOML
[dev-dependencies]
rassert = "1"
~~~~

Then simply import the `prelude` module in your tests:

~~~~Rust
#[cfg(test)]

mod tests {
    use rassert::prelude::*;

    #[test]
    fn rassert_works() {
        expect!(true)
            .not()
            .to_be_false()
            .and()
            .to_equal(true)
            .conclude_panic();
    }
}
~~~~

## Writing Assertions


### Expectation Chains


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)`
    * Creates a new chain asserting against the provided `expression`.
  * `expect_matches!(expression, pattern)`
    * Creates a new chain from the provided `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:

~~~~Rust
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.

### Concluding Chains


Since rassert evaluates expectations lazily, a chain like the above one will do nothing. A chain will only assert the specified expectations when concluded:

~~~~Rust
// 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();
~~~~

### Soft Mode


A chain can be put into soft mode by calling `soft()` prior to concluding the chain:

~~~~Rust
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.

### Negating Expectations


One can negate a single subsequent expectation using the `not()` function:

~~~~Rust
expect!(true)
    .not()
    .to_be_false()
    .conclude_panic();
~~~~

If one wishes to negate additionals expectations, then `not()` has to be applied again.

## Available Expectations


  * `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


Custom expectations can be written as [extension traits](https://rust-lang.github.io/rfcs/0445-extension-trait-conventions.html) 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`:

~~~~Rust
#[derive(Debug)]

pub struct Pizza {
    pub flavor: String
}
~~~~

In rassert, expectations are actually structs, following something like a [Command pattern](https://sourcemaking.com/design_patterns/command). Given, we want to check what flavor of Pizza we have, we can create something as follows:

~~~~Rust
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`
    * The actual assertion which returns true on success and false otherwise. The `actual` parameter corresponds to the value being tested in the chain.
  * `fn(&self, expression, actual)` -> String
    * The message shown if the expectation fails. The `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:

~~~~Rust
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:

  * Extension traits must take a generic lifetime parameter and use it in the `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.
  * Expectation functions must take `self` since expectation chains are [Consuming builders]https://doc.rust-lang.org/1.0.0/style/ownership/builders.html.
  * Expectation functions can extend the chain with a new expectation using the `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.

The built-in expectations of the [src/expectations](src/expectations) directory also use the above facilities, therefore they serve as a great starting point for writing custom expectations.

## License


Licensed under [The MIT License](LICENSE)