|
| 1 | +//! This example demonstrates how to write a custom assertion for a custom |
| 2 | +//! struct by reusing existing assertion methods. |
| 3 | +//! |
| 4 | +//! We define the extension trait `AssertSnake` with the custom assertion method |
| 5 | +//! `has_body`. This trait is implemented for `Spec`. The implementation reuses |
| 6 | +//! built-in assertions on each field of the snake. In case of failing |
| 7 | +//! assertions, the names of the fields are printed with the failure message. |
| 8 | +//! |
| 9 | +//! The custom assertion method does not fail on the first failure but collects |
| 10 | +//! all failures first and then fails, according to the current |
| 11 | +//! `FailingStrategy` of the `Spec`. |
| 12 | +//! |
| 13 | +//! Running the example will print a failed assertion to the console: |
| 14 | +//! |
| 15 | +//! ```console |
| 16 | +//! thread 'main' (28052) panicked at examples\custom_assertion_reusing_existing.rs:122:25: |
| 17 | +//! assertion of snake body failed: |
| 18 | +//! |
| 19 | +//! expected snake.length to be equal to 3 |
| 20 | +//! but was: 2 |
| 21 | +//! expected: 3 |
| 22 | +//! |
| 23 | +//! expected snake.body to contain exactly in order [Coord { x: 2, y: 1 }, Coord { x: 1, y: 1 }, Coord { x: 1, y: 2 }] |
| 24 | +//! but was: [Coord { x: 2, y: 1 }, Coord { x: 1, y: 2 }, Coord { x: -1, y: 1 }] |
| 25 | +//! expected: [Coord { x: 2, y: 1 }, Coord { x: 1, y: 1 }, Coord { x: 1, y: 2 }] |
| 26 | +//! missing: [Coord { x: 1, y: 1 }] |
| 27 | +//! extra: [Coord { x: -1, y: 1 }] |
| 28 | +//! out-of-order: [Coord { x: 1, y: 2 }] |
| 29 | +//! |
| 30 | +//! expected snake.head to be equal to Coord { x: 2, y: 1 } |
| 31 | +//! but was: Coord { x: 3, y: 1 } |
| 32 | +//! expected: Coord { x: 2, y: 1 } |
| 33 | +//! ``` |
| 34 | +//! |
| 35 | +//! [image of colored output in the console](custom_assertion_reusing_existing.png) |
| 36 | +
|
| 37 | +// just to prevent some linter warnings |
| 38 | +mod fixture; |
| 39 | + |
| 40 | +use asserting::prelude::*; |
| 41 | +use asserting::spec::{FailingStrategy, Spec}; |
| 42 | +use std::borrow::Borrow; |
| 43 | + |
| 44 | +#[derive(Debug, Clone, Copy, PartialEq)] |
| 45 | +struct Coord { |
| 46 | + x: i32, |
| 47 | + y: i32, |
| 48 | +} |
| 49 | + |
| 50 | +struct Snake { |
| 51 | + length: usize, |
| 52 | + head: Coord, |
| 53 | + body: Vec<Coord>, |
| 54 | +} |
| 55 | + |
| 56 | +/// Custom assertion methods for `Snake`. |
| 57 | +trait AssertSnake { |
| 58 | + #[track_caller] |
| 59 | + fn has_body(self, expected_body: &[Coord]) -> Self; |
| 60 | +} |
| 61 | + |
| 62 | +// we implement the `AssertSnake` trait for a generic `S: Borrow<Snake>` so that |
| 63 | +// the assertion method `has_body` can be called on owned and borrowed `Snake` |
| 64 | +// instances. |
| 65 | +impl<S, R> AssertSnake for Spec<'_, S, R> |
| 66 | +where |
| 67 | + S: Borrow<Snake>, |
| 68 | + R: FailingStrategy, |
| 69 | +{ |
| 70 | + fn has_body(mut self, expected_body: &[Coord]) -> Self { |
| 71 | + let actual_body = self.subject().borrow(); |
| 72 | + // we first collect all failures using the "soft assertion" mode of |
| 73 | + // asserting, which is started by using the `verify_that` function. |
| 74 | + let mut failures; |
| 75 | + failures = verify_that(actual_body) |
| 76 | + // `verify_that` does not highlight differences by default, so we |
| 77 | + // switch on highlighting using the configured `DiffFormat` |
| 78 | + .with_configured_diff_format() |
| 79 | + .extracting(|s| s.length) |
| 80 | + .named("snake.length") |
| 81 | + .is_equal_to(expected_body.len()) |
| 82 | + .display_failures(); |
| 83 | + failures.extend( |
| 84 | + verify_that(actual_body) |
| 85 | + .with_configured_diff_format() |
| 86 | + .extracting(|s| &s.body) |
| 87 | + .named("snake.body") |
| 88 | + .contains_exactly(expected_body) |
| 89 | + .display_failures(), |
| 90 | + ); |
| 91 | + failures.extend( |
| 92 | + verify_that(actual_body) |
| 93 | + .with_configured_diff_format() |
| 94 | + .extracting(|s| s.head) |
| 95 | + .named("snake.head") |
| 96 | + .is_equal_to(expected_body[0]) |
| 97 | + .display_failures(), |
| 98 | + ); |
| 99 | + // if there are failures, we fail the whole assertion according to the |
| 100 | + // current `FailingStrategy`. |
| 101 | + if !failures.is_empty() { |
| 102 | + self.do_fail_with_message(format!( |
| 103 | + "assertion of snake body failed: \n\n{}", |
| 104 | + failures.join("\n") |
| 105 | + )); |
| 106 | + } |
| 107 | + self |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +fn test() { |
| 112 | + let snake = Snake { |
| 113 | + length: 2, |
| 114 | + head: Coord { x: 3, y: 1 }, |
| 115 | + body: vec![ |
| 116 | + Coord { x: 2, y: 1 }, |
| 117 | + Coord { x: 1, y: 2 }, |
| 118 | + Coord { x: -1, y: 1 }, |
| 119 | + ], |
| 120 | + }; |
| 121 | + |
| 122 | + assert_that!(snake).has_body(&[ |
| 123 | + Coord { x: 2, y: 1 }, |
| 124 | + Coord { x: 1, y: 1 }, |
| 125 | + Coord { x: 1, y: 2 }, |
| 126 | + ]); |
| 127 | +} |
| 128 | + |
| 129 | +fn main() { |
| 130 | + test(); |
| 131 | +} |
0 commit comments