Skip to main content

fluent_assertions/assertions/
collection_assertion.rs

1use super::Assertion;
2use std::fmt::Debug;
3
4/// Specific assertions for list-like collections (`Vec`, arrays and slices).
5///
6/// These live on a trait rather than being inherent methods because the string
7/// assertions are implemented for the blanket `impl<T: AsRef<str>> Assertion<T>`.
8/// Coherence conservatively assumes `Vec<_>` / `&[_]` might implement
9/// `AsRef<str>` in the future, so inherent methods sharing the same names
10/// (`be_empty`, `contain`, ...) would clash with the string impl. A trait sidesteps
11/// that: it is implemented only for the concrete `Assertion<Vec<T>>`,
12/// `Assertion<&[T]>`, `Assertion<[T; N]>` and `Assertion<&Vec<T>>`, never for
13/// `&str` / `String`.
14pub trait CollectionAssertion<T> {
15    /// Asserts that the collection is empty
16    ///
17    /// # Examples
18    ///
19    /// ```
20    /// use fluent_assertions::*;
21    /// Vec::<i32>::new().should().be_empty();
22    /// ```
23    fn be_empty(self) -> Self;
24    /// Asserts that the collection is not empty
25    ///
26    /// # Examples
27    ///
28    /// ```
29    /// use fluent_assertions::*;
30    /// vec![1, 2, 3].should().not_be_empty();
31    /// ```
32    fn not_be_empty(self) -> Self;
33    /// Asserts that the collection has a given length
34    ///
35    /// # Examples
36    ///
37    /// ```
38    /// use fluent_assertions::*;
39    /// [1, 2, 3].should().have_length(3);
40    /// ```
41    fn have_length(self, length: usize) -> Self;
42    /// Asserts that the collection contains a given element
43    ///
44    /// # Examples
45    ///
46    /// ```
47    /// use fluent_assertions::*;
48    /// vec![1, 2, 3].should().contain(&2);
49    /// ```
50    fn contain(self, expected: &T) -> Self
51    where
52        T: PartialEq;
53}
54
55#[track_caller]
56fn assert_empty<T: Debug>(items: &[T]) {
57    assert!(
58        items.is_empty(),
59        "Expected collection to be empty, but got {:?}",
60        items
61    );
62}
63
64#[track_caller]
65fn assert_not_empty<T>(items: &[T]) {
66    assert!(
67        !items.is_empty(),
68        "Expected collection to not be empty, but got empty collection"
69    );
70}
71
72#[track_caller]
73fn assert_length<T>(items: &[T], length: usize) {
74    assert!(
75        items.len() == length,
76        "Expected collection to have length {}, but it had length {}",
77        length,
78        items.len()
79    );
80}
81
82#[track_caller]
83fn assert_contains<T: PartialEq + Debug>(items: &[T], expected: &T) {
84    assert!(
85        items.contains(expected),
86        "Expected collection to contain {:?}, but it didn't",
87        expected
88    );
89}
90
91/// Generates a `CollectionAssertion` impl that forwards to the shared helpers.
92///
93/// `$slice` is how the wrapped value is viewed as a `&[T]`; it varies per type
94/// (owned collections borrow, references pass through) so it is supplied per impl.
95macro_rules! impl_collection_assertion {
96    ([$($generics:tt)*] $ty:ty, |$this:ident| $slice:expr) => {
97        impl<$($generics)*> CollectionAssertion<T> for Assertion<$ty> {
98            #[track_caller]
99            fn be_empty(self) -> Self {
100                let $this = &self;
101                assert_empty($slice);
102                self
103            }
104
105            #[track_caller]
106            fn not_be_empty(self) -> Self {
107                let $this = &self;
108                assert_not_empty($slice);
109                self
110            }
111
112            #[track_caller]
113            fn have_length(self, length: usize) -> Self {
114                let $this = &self;
115                assert_length($slice, length);
116                self
117            }
118
119            #[track_caller]
120            fn contain(self, expected: &T) -> Self
121            where
122                T: PartialEq,
123            {
124                let $this = &self;
125                assert_contains($slice, expected);
126                self
127            }
128        }
129    };
130}
131
132impl_collection_assertion!([T: Debug] Vec<T>, |a| &a.value);
133impl_collection_assertion!([T: Debug] &[T], |a| a.value);
134impl_collection_assertion!([T: Debug, const N: usize] [T; N], |a| &a.value);
135impl_collection_assertion!([T: Debug] &Vec<T>, |a| a.value);
136
137#[cfg(test)]
138mod tests {
139    use crate::assertions::*;
140    use rstest::*;
141
142    #[test]
143    fn test_vec_assertions() {
144        let numbers = vec![1, 2, 3];
145        numbers.should().not_be_empty().have_length(3).contain(&2);
146    }
147
148    #[test]
149    fn test_slice_assertions() {
150        let numbers = [1, 2, 3];
151        numbers
152            .as_slice()
153            .should()
154            .not_be_empty()
155            .have_length(3)
156            .contain(&2);
157    }
158
159    #[test]
160    fn test_array_assertions() {
161        [1, 2, 3].should().not_be_empty().have_length(3).contain(&2);
162    }
163
164    #[test]
165    fn test_vec_ref_assertions() {
166        let numbers = vec![1, 2, 3];
167        (&numbers)
168            .should()
169            .not_be_empty()
170            .have_length(3)
171            .contain(&2);
172    }
173
174    #[rstest]
175    #[case(Vec::<i32>::new())]
176    #[case(vec![])]
177    fn should_be_empty(#[case] input: Vec<i32>) {
178        input.should().be_empty();
179    }
180
181    #[test]
182    fn empty_slice_should_be_empty() {
183        let empty: [i32; 0] = [];
184        empty.as_slice().should().be_empty();
185    }
186
187    #[test]
188    fn empty_array_should_be_empty() {
189        let empty: [i32; 0] = [];
190        empty.should().be_empty();
191    }
192
193    #[rstest]
194    #[case(vec!["hello".to_string()])]
195    fn should_contain_string(#[case] input: Vec<String>) {
196        input.should().contain(&String::from("hello"));
197    }
198
199    #[test]
200    #[should_panic(expected = "Expected collection to contain 4")]
201    fn contain_panics_when_element_missing() {
202        vec![1, 2, 3].should().contain(&4);
203    }
204
205    #[test]
206    #[should_panic(expected = "Expected collection to be empty")]
207    fn be_empty_panics_when_not_empty() {
208        vec![1].should().be_empty();
209    }
210
211    #[test]
212    #[should_panic(expected = "Expected collection to have length 5, but it had length 3")]
213    fn have_length_panics_on_mismatch() {
214        vec![1, 2, 3].should().have_length(5);
215    }
216}