Module xpct::docs::tutorial

source ·
Available on docsrs only.
Expand description

Tutorial

A brief guide on how to use xpct to write tests.

↩︎ Back to User Docs

To make an assertion, you’ll usually start with the expect! macro, which returns an Assertion.

use xpct::{expect, equal};

expect!("Disco").to(equal("Disco"));

In the above example, equal is a matcher. This crate provides a number of matchers, and you can implement custom matchers as well.

When an assertion fails, it panics with an error message.

You can also chain matchers like this:

use xpct::{expect, be_gt, be_lt};

expect!(41)
    .to(be_gt(0)) // 41 > 0
    .to(be_lt(57)); // 41 < 57

When you chain together multiple matchers like this, the assertion only succeeds if all of them succeed.

You can also negate matchers by calling Assertion::to_not or using the not matcher:

use xpct::{expect, equal, not};

// These are equivalent.
expect!(41).to_not(equal(57));
expect!(41).to(not(equal(57)));

Some matchers are actually just aliases for negating other matchers:

use xpct::{expect, be_some, be_none};

let value: Option<&str> = None;

// These are equivalent.
expect!(value).to(be_none());
expect!(value).to_not(be_some());

When you chain together matchers, they pass the value you passed to expect! into the next matcher in the chain. Matchers can change the type of this value, which allows some matchers to do things like unwrap Result and Option types.

use std::io;
use xpct::{expect, equal, be_ok};

fn location() -> io::Result<String> {
    Ok(String::from("Martinaise"))
}

expect!(location())
    .to(be_ok())
    .to(equal("Martinaise"));

In the above example, we don’t need to unwrap the Result, because the be_ok matcher did it for us! If we were to negate this matcher with not (or just use be_err), then it would return the value of the Err variant instead.

If you want to map a value by applying a function to it as part of a chain of matchers, you can use the matchers map and try_map as well as Assertion::map and Assertion::try_map.

use std::convert::Infallible;

use xpct::{expect, map, try_map, equal};

struct Name(String);

expect!(Name(String::from("Cuno")))
    .map(|name| name.0)
    .to(equal("Cuno"));

// We use `try_map` for conversions that can fail.
expect!(vec![0x43, 0x75, 0x6e, 0x6f])
    .try_map(|bytes| Ok(String::from_utf8(bytes)?))
    .to(equal("Cuno"));

If you need to convert between types that implement From or TryFrom, you can use the matchers into and try_into as well as Assertion::into and Assertion::try_into.

use xpct::{expect, equal};

expect!(41u64)
    .try_into::<u32>()
    .to(equal(41u32));

You can always get the value back out at the end of a chain of matchers by calling Assertion::into_inner. This lets you use the same value in another assertion.

use xpct::{be_some, equal, expect, have_len};

let name = expect!(["Mañana", "Evrart"])
    .to(have_len(2))
    .into_inner();

expect!(name.first())
    .to(be_some())
    .to(equal(&"Mañana"));

There are combinator matchers like all, each, and any which allow you to combine matchers in different ways:

use xpct::{expect, any, equal, be_none};

fn description() -> Option<String> {
    None
}

// This is either `None` or `Some("horrific")`.
expect!(description()).to(any(|ctx| {
    ctx.map(Option::as_deref)
        .to(be_none())
        .to(equal(Some("horrific")));
}));

If you want to attach additional context to a matcher to include in the failure output, you can use why and why_lazy:

use xpct::{expect, match_regex, why};

expect!("Kim").to(why(
    match_regex(r"^\p{Lu}"),
    "names should start with a capital letter",
));

You can test that a value matches a pattern using the match_pattern matcher and the pattern! macro.

use xpct::{expect, match_pattern, pattern};

#[derive(Debug)]
enum Command {
    Create(String),
    Update(String),
    Delete,
}

let command = Command::Create("foo".into());

expect!(command).to(match_pattern(pattern!(
    Command::Create(_) | Command::Delete
)));

You can use eq_diff instead of equal for any type that implements Diffable to print a rich diff when the values are not equal.

use xpct::{expect, eq_diff};

expect!("diffing strings").to(eq_diff("diffing strings"));
expect!(["slices", "too"]).to(eq_diff(["slices", "too"]));
// Also sets and maps!

If you want to assert on multiple fields of a struct, rather than using a separate expect! assertion for each field, you can use match_fields with the fields! macro.

use xpct::{be_gt, be_some, be_true, expect, fields, have_prefix, match_fields};

struct Person {
    id: String,
    name: Option<String>,
    age: u32,
    is_superstar: bool,
}

let value = Person {
    id: String::from("REV12-62-05-JAM41"),
    name: Some(String::from("Raphaël")),
    age: 44,
    is_superstar: true,
};

expect!(value).to(match_fields(fields!(Person {
    id: have_prefix("REV"),
    name: be_some(),
    age: be_gt(0),
    is_superstar: be_true(),
})));

There are also a number of matchers for dealing with collections. For example, you can assert that a collection contains certain elements using contain_element and contain_elements.

use xpct::{contain_element, expect};

expect!(["Mañana", "Evrart"]).to(contain_element("Evrart"));

You can also use consist_of to assert that a collection consists of exactly the given elements, in any order.

use xpct::{consist_of, expect};

expect!(&["Mañana", "Evrart"]).to(consist_of(["Evrart", "Mañana"]));

The be_in matcher can test that something is in a collection.

use xpct::{be_in, expect};

expect!("Mañana").to(be_in(["Evrart", "Mañana"]));
expect!('C').to(be_in("Cuno"));
expect!(50).to(be_in(41..57));

The every matcher allows you to test every element in a collection against the same matcher.

use xpct::{be_some, every, expect, have_prefix};

let items = vec![Some("Cuno"), Some("Cindy")];

// Notice it unwraps the `Vec<Option<&str>>` to a `Vec<&str>`.
let unwrapped: Vec<&str> = expect!(items)
    .to(every(be_some))
    .into_inner();

The match_elements matcher allows you to tests each element of a collection against a different matcher.

use xpct::{be_in, equal, expect, have_prefix, match_elements};

let items = vec!["apple", "banana", "cucumber"];

expect!(items).to(match_elements([
    equal("apple"),
    be_in(["banana", "orange"]),
    have_prefix("c"),
]));

The matchers for collections are implemented using the Len and Contains traits. You can implement these traits for your own types to use them with the collections matchers.

Check out Provided Matchers for a list of all the matchers provided by this crate.