1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! Custom matcher cookbook.
//!
//! The built-in matchers (`eq`, `contains`, `matches_struct!`, and the rest)
//! cover most assertions, but a test suite for a real domain eventually wants
//! its own vocabulary: `is_freezing()`, `is_a_valid_iban()`, `settled()`. A
//! custom matcher is reusable, composes with the combinators (`not`, `all_of`,
//! `some`, ...), and produces a failure message written in domain terms rather
//! than in terms of raw field values.
//!
//! This module is documentation only. It contains no code; the runnable
//! version of everything below lives in `examples/custom-matcher/`.
//!
//! # The two ways to write one
//!
//! 1. [`define_matcher!`](crate::define_matcher) is the declarative shortcut.
//! Reach for it when the matcher is a predicate plus a description and
//! nothing more. It is the right tool for the large majority of cases.
//! 2. A hand-written `impl Matcher<T>` gives full control: a structured diff,
//! an inner matcher applied to a projection, a description assembled from
//! parts. Reach for it when the shortcut is not enough.
//!
//! Before writing either, check whether a built-in already fits. To assert on
//! the *shape* of a struct, tuple, or enum variant, the structural macros
//! ([`matches_struct!`](crate::matches_struct),
//! [`matches_tuple!`](crate::matches_tuple),
//! [`matches_variant!`](crate::matches_variant)) compose existing matchers and
//! need no new type. To wrap an ad-hoc closure once, without naming it,
//! [`predicate`](crate::predicate) is lighter still.
//!
//! # The `Matcher` trait
//!
//! A matcher is any type that implements [`Matcher<T>`](crate::Matcher):
//!
//! ```ignore
//! pub trait Matcher<T: ?Sized> {
//! fn check(&self, actual: &T) -> MatchResult;
//! fn description(&self) -> Description;
//! }
//! ```
//!
//! - `check` inspects the borrowed value and returns a
//! [`MatchResult`](crate::MatchResult): either
//! [`MatchResult::pass()`](crate::MatchResult::pass) or
//! [`MatchResult::fail(mismatch)`](crate::MatchResult::fail).
//! - `description` returns a [`Description`](crate::Description), the composable
//! account of what the matcher expects. It is used both in the failure
//! message and by combinators that wrap this matcher.
//!
//! A failure carries a [`Mismatch`](crate::Mismatch): the `expected`
//! description, the `actual` value rendered with `{:?}`, and an optional
//! pre-rendered `diff`. The usual way to build one is
//! [`Mismatch::new(expected, actual)`](crate::Mismatch::new), optionally
//! followed by [`.with_diff(...)`](crate::Mismatch::with_diff).
//!
//! # The declarative shortcut
//!
//! [`define_matcher!`](crate::define_matcher) writes the type, the trait impl,
//! and the constructor for you. The matcher inspects a value of a concrete
//! type and answers yes or no, with a fixed description:
//!
//! ```
//! use test_better::prelude::*;
//! use test_better::define_matcher;
//!
//! define_matcher! {
//! /// Matches a temperature, in degrees Celsius, at or below freezing.
//! pub fn is_freezing for f64 {
//! expects: "a temperature at or below 0\u{b0}C",
//! matches: |celsius| *celsius <= 0.0,
//! }
//! }
//!
//! fn main() -> TestResult {
//! check!(-4.0_f64).satisfies(is_freezing())?;
//! check!(20.0_f64).violates(is_freezing())?;
//! Ok(())
//! }
//! ```
//!
//! Constructor parameters are supported; each is in scope inside `expects` and
//! `matches` as a value of its declared type (so the parameter types must be
//! [`Clone`]):
//!
//! ```
//! use test_better::prelude::*;
//! use test_better::define_matcher;
//!
//! define_matcher! {
//! /// Matches a vector whose length is a multiple of `n`.
//! pub fn len_multiple_of(n: usize) for Vec<i32> {
//! expects: format!("a vector whose length is a multiple of {n}"),
//! matches: |v| v.len() % n == 0,
//! }
//! }
//!
//! fn main() -> TestResult {
//! check!(vec![1, 2, 3, 4]).satisfies(len_multiple_of(2))?;
//! Ok(())
//! }
//! ```
//!
//! The target type must implement [`Debug`](std::fmt::Debug), since a failure
//! reports the actual value through `{:?}`.
//!
//! # The hand-written form
//!
//! When the matcher needs more than a predicate, implement the trait directly.
//! The pattern is a (usually small) struct, an `impl Matcher<T>` for it, and a
//! lower-case constructor function returning `impl Matcher<T>` so callers never
//! name the struct:
//!
//! ```
//! use test_better::prelude::*;
//! use test_better::{Description, MatchResult, Matcher, Mismatch};
//!
//! /// A temperature reading, in degrees Celsius.
//! #[derive(Debug)]
//! struct Temperature(f64);
//!
//! struct IsFreezing;
//!
//! impl Matcher<Temperature> for IsFreezing {
//! fn check(&self, actual: &Temperature) -> MatchResult {
//! if actual.0 <= 0.0 {
//! MatchResult::pass()
//! } else {
//! MatchResult::fail(Mismatch::new(
//! self.description(),
//! format!("{:.1}\u{b0}C, which is above freezing", actual.0),
//! ))
//! }
//! }
//!
//! fn description(&self) -> Description {
//! Description::text("a temperature at or below 0\u{b0}C")
//! }
//! }
//!
//! /// Matches a [`Temperature`] at or below freezing.
//! fn is_freezing() -> impl Matcher<Temperature> {
//! IsFreezing
//! }
//!
//! fn main() -> TestResult {
//! check!(Temperature(-4.0)).satisfies(is_freezing())?;
//! Ok(())
//! }
//! ```
//!
//! Two things the hand-written form unlocks that the shortcut does not:
//!
//! - **A description built from parts.** `Description` composes with `and`,
//! `or`, `labeled`, and `!`; a matcher that takes an inner matcher can fold
//! the inner matcher's own `description()` into its own, so a nested failure
//! reads as nested.
//! - **A control over the `actual` rendering and the diff.** `check` decides
//! exactly what string the failure shows, and may attach a diff with
//! [`Mismatch::with_diff`](crate::Mismatch::with_diff).
//!
//! # Matchers that take an inner matcher
//!
//! A matcher generic over `M: Matcher<Inner>` applies `M` to some projection of
//! the actual value. This is how `some`, `ok`, and the structural macros are
//! built. The key move is to wrap the inner matcher's failure in a
//! [`labeled`](crate::Description::labeled) description so the output keeps the
//! layer that failed:
//!
//! ```
//! use test_better::prelude::*;
//! use test_better::{Description, MatchResult, Matcher, Mismatch};
//!
//! #[derive(Debug)]
//! struct Celsius(f64);
//!
//! struct AsFloat<M>(M);
//!
//! impl<M: Matcher<f64>> Matcher<Celsius> for AsFloat<M> {
//! fn check(&self, actual: &Celsius) -> MatchResult {
//! let inner = self.0.check(&actual.0);
//! match inner.failure {
//! None => MatchResult::pass(),
//! Some(mismatch) => MatchResult::fail(Mismatch {
//! expected: Description::labeled("degrees", mismatch.expected),
//! ..mismatch
//! }),
//! }
//! }
//!
//! fn description(&self) -> Description {
//! Description::labeled("degrees", self.0.description())
//! }
//! }
//!
//! /// Applies `inner` to a [`Celsius`] reading's underlying value.
//! fn as_float<M: Matcher<f64>>(inner: M) -> impl Matcher<Celsius> {
//! AsFloat(inner)
//! }
//!
//! fn main() -> TestResult {
//! check!(Celsius(3.5)).satisfies(as_float(gt(0.0)))?;
//! Ok(())
//! }
//! ```
//!
//! # Where to go next
//!
//! `examples/custom-matcher/` is a small crate that builds and tests every
//! pattern above. It is the place to copy from when starting a real custom
//! matcher.