xpct/docs/
tutorial.rs

1/*!
2# Tutorial
3
4A brief guide on how to use xpct to write tests.
5
6[↩︎ Back to User Docs](crate::docs)
7
8To make an assertion, you'll usually start with the [`expect!`] macro, which
9returns an [`Assertion`].
10
11```
12use xpct::{expect, equal};
13
14expect!("Disco").to(equal("Disco"));
15```
16
17In the above example, [`equal`] is a *matcher*. This crate provides [a number of
18matchers][crate::docs::matcher_list], and you can implement custom matchers as
19well.
20
21When an assertion fails, it panics with an error message.
22
23You can also chain matchers like this:
24
25```
26use xpct::{expect, be_gt, be_lt};
27
28expect!(41)
29    .to(be_gt(0)) // 41 > 0
30    .to(be_lt(57)); // 41 < 57
31```
32
33When you chain together multiple matchers like this, the assertion only succeeds
34if *all* of them succeed.
35
36You can also negate matchers by calling [`Assertion::to_not`] or using the
37[`not`] matcher:
38
39```
40use xpct::{expect, equal, not};
41
42// These are equivalent.
43expect!(41).to_not(equal(57));
44expect!(41).to(not(equal(57)));
45```
46
47Some matchers are actually just aliases for negating other matchers:
48
49```
50use xpct::{expect, be_some, be_none};
51
52let value: Option<&str> = None;
53
54// These are equivalent.
55expect!(value).to(be_none());
56expect!(value).to_not(be_some());
57```
58
59When you chain together matchers, they pass the value you passed to [`expect!`]
60into the next matcher in the chain. Matchers can change the type of this value,
61which allows some matchers to do things like unwrap [`Result`] and [`Option`]
62types.
63
64```
65use std::io;
66use xpct::{expect, equal, be_ok};
67
68fn location() -> io::Result<String> {
69    Ok(String::from("Martinaise"))
70}
71
72expect!(location())
73    .to(be_ok())
74    .to(equal("Martinaise"));
75```
76
77In the above example, we don't need to unwrap the [`Result`], because the
78[`be_ok`] matcher did it for us! If we were to negate this matcher with [`not`]
79(or just use [`be_err`]), then it would return the value of the [`Err`] variant
80instead.
81
82If you want to map a value by applying a function to it as part of a chain of
83matchers, you can use the matchers [`map`] and [`try_map`] as well as
84[`Assertion::map`] and [`Assertion::try_map`].
85
86```
87use std::convert::Infallible;
88
89use xpct::{expect, map, try_map, equal};
90
91struct Name(String);
92
93expect!(Name(String::from("Cuno")))
94    .map(|name| name.0)
95    .to(equal("Cuno"));
96
97// We use `try_map` for conversions that can fail.
98expect!(vec![0x43, 0x75, 0x6e, 0x6f])
99    .try_map(|bytes| Ok(String::from_utf8(bytes)?))
100    .to(equal("Cuno"));
101```
102
103If you need to convert between types that implement [`From`] or [`TryFrom`], you
104can use the matchers [`into`] and [`try_into`] as well as [`Assertion::into`]
105and [`Assertion::try_into`].
106
107```
108use xpct::{expect, equal};
109
110expect!(41u64)
111    .try_into::<u32>()
112    .to(equal(41u32));
113```
114
115You can always get the value back out at the end of a chain of matchers by
116calling [`Assertion::into_inner`]. This lets you use the same value in another
117assertion.
118
119```
120use xpct::{be_some, equal, expect, have_len};
121
122let name = expect!(["Mañana", "Evrart"])
123    .to(have_len(2))
124    .into_inner();
125
126expect!(name.first())
127    .to(be_some())
128    .to(equal(&"Mañana"));
129```
130
131There are combinator matchers like [`all`], [`each`], and [`any`] which allow
132you to combine matchers in different ways:
133
134```
135use xpct::{expect, any, equal, be_none};
136
137fn description() -> Option<String> {
138    None
139}
140
141// This is either `None` or `Some("horrific")`.
142expect!(description()).to(any(|ctx| {
143    ctx.map(Option::as_deref)
144        .to(be_none())
145        .to(equal(Some("horrific")));
146}));
147
148```
149
150If you want to attach additional context to a matcher to include in the failure
151output, you can use [`why`] and [`why_lazy`]:
152
153```
154use xpct::{expect, match_regex, why};
155
156expect!("Kim").to(why(
157    match_regex(r"^\p{Lu}"),
158    "names should start with a capital letter",
159));
160```
161
162You can test that a value matches a pattern using the [`match_pattern`] matcher
163and the [`pattern!`] macro.
164
165```
166use xpct::{expect, match_pattern, pattern};
167
168#[derive(Debug)]
169enum Command {
170    Create(String),
171    Update(String),
172    Delete,
173}
174
175let command = Command::Create("foo".into());
176
177expect!(command).to(match_pattern(pattern!(
178    Command::Create(_) | Command::Delete
179)));
180```
181
182You can use [`eq_diff`] instead of [`equal`] for any type that implements
183[`Diffable`] to print a rich diff when the values are not equal.
184
185```
186use xpct::{expect, eq_diff};
187
188expect!("diffing strings").to(eq_diff("diffing strings"));
189expect!(["slices", "too"]).to(eq_diff(["slices", "too"]));
190// Also sets and maps!
191```
192
193If you want to assert on multiple fields of a struct, rather than using a
194separate [`expect!`] assertion for each field, you can use [`match_fields`] with
195the [`fields!`] macro.
196
197```
198use xpct::{be_gt, be_some, be_true, expect, fields, have_prefix, match_fields};
199
200struct Person {
201    id: String,
202    name: Option<String>,
203    age: u32,
204    is_superstar: bool,
205}
206
207let value = Person {
208    id: String::from("REV12-62-05-JAM41"),
209    name: Some(String::from("Raphaël")),
210    age: 44,
211    is_superstar: true,
212};
213
214expect!(value).to(match_fields(fields!(Person {
215    id: have_prefix("REV"),
216    name: be_some(),
217    age: be_gt(0),
218    is_superstar: be_true(),
219})));
220```
221
222There are also a number of matchers for dealing with collections. For example,
223you can assert that a collection contains certain elements using
224[`contain_element`] and [`contain_elements`].
225
226```
227use xpct::{contain_element, expect};
228
229expect!(["Mañana", "Evrart"]).to(contain_element("Evrart"));
230```
231
232You can also use [`consist_of`] to assert that a collection consists of exactly
233the given elements, in any order.
234
235```
236use xpct::{consist_of, expect};
237
238expect!(&["Mañana", "Evrart"]).to(consist_of(["Evrart", "Mañana"]));
239```
240
241The [`be_in`] matcher can test that something is in a collection.
242
243```
244use xpct::{be_in, expect};
245
246expect!("Mañana").to(be_in(["Evrart", "Mañana"]));
247expect!('C').to(be_in("Cuno"));
248expect!(50).to(be_in(41..57));
249```
250
251The [`every`] matcher allows you to test every element in a collection against
252the same matcher.
253
254```
255use xpct::{be_some, every, expect, have_prefix};
256
257let items = vec![Some("Cuno"), Some("Cindy")];
258
259// Notice it unwraps the `Vec<Option<&str>>` to a `Vec<&str>`.
260let unwrapped: Vec<&str> = expect!(items)
261    .to(every(be_some))
262    .into_inner();
263```
264
265The [`match_elements`] matcher allows you to tests each element of a collection
266against a different matcher.
267
268```
269use xpct::{be_in, equal, expect, have_prefix, match_elements};
270
271let items = vec!["apple", "banana", "cucumber"];
272
273expect!(items).to(match_elements([
274    equal("apple"),
275    be_in(["banana", "orange"]),
276    have_prefix("c"),
277]));
278```
279
280The matchers for collections are implemented using the [`Len`] and [`Contains`]
281traits. You can implement these traits for your own types to use them with the
282collections matchers.
283
284Check out [Provided Matchers][crate::docs::matcher_list] for a list of all the
285matchers provided by this crate.
286
287[`equal`]: crate::equal
288[`eq_diff`]: crate::eq_diff
289[`not`]: crate::not
290[`be_ok`]: crate::be_ok
291[`be_err`]: crate::be_err
292[`map`]: crate::map
293[`try_map`]: crate::try_map
294[`into`]: crate::into
295[`try_into`]: crate::try_into
296[`all`]: crate::all
297[`each`]: crate::each
298[`any`]: crate::any
299[`why`]: crate::why
300[`why_lazy`]: crate::why_lazy
301[`match_pattern`]: crate::match_pattern
302[`Diffable`]: crate::matchers::diff::Diffable
303[`pattern!`]: crate::pattern
304[`match_fields`]: crate::match_fields
305[`expect!`]: crate::expect
306[`fields!`]: crate::fields
307[`contain_element`]: crate::contain_element
308[`contain_elements`]: crate::contain_elements
309[`consist_of`]: crate::consist_of
310[`be_in`]: crate::be_in
311[`every`]: crate::every
312[`match_elements`]: crate::match_elements
313[`Len`]: crate::matchers::collections::Len
314[`Contains`]: crate::matchers::collections::Contains
315[`Assertion`]: crate::core::Assertion
316[`Assertion::to_not`]: crate::core::Assertion::to_not
317[`Matcher`]: crate::core::Matcher
318[`Assertion::into_inner`]: crate::core::Assertion::into_inner
319[`Assertion::map`]: crate::core::Assertion::map
320[`Assertion::try_map`]: crate::core::Assertion::try_map
321[`Assertion::into`]: crate::core::Assertion::into
322[`Assertion::try_into`]: crate::core::Assertion::try_into
323*/