assertr/assertions/std/
hashmap.rs

1use crate::{AssertThat, AssertrPartialEq, EqContext, Mode, tracking::AssertionTracking};
2use core::borrow::Borrow;
3use core::fmt::Debug;
4use core::fmt::Write;
5use indoc::writedoc;
6use std::{collections::HashMap, hash::Hash};
7
8/// Assertions for generic [HashMap]s.
9pub trait HashMapAssertions<K, V> {
10    fn contains_key(self, expected: impl Borrow<K>) -> Self
11    where
12        K: Eq + Hash + Debug,
13        V: Debug;
14
15    fn does_not_contain_key(self, not_expected: impl Borrow<K>) -> Self
16    where
17        K: Eq + Hash + Debug,
18        V: Debug;
19
20    fn contains_value<E>(self, expected: E) -> Self
21    where
22        K: Debug,
23        V: AssertrPartialEq<E> + Debug,
24        E: Debug;
25
26    fn contains_entry<E>(self, key: impl Borrow<K>, value: impl Borrow<E>) -> Self
27    where
28        K: Eq + Hash + Debug,
29        V: AssertrPartialEq<E> + Debug,
30        E: Debug;
31}
32
33impl<K, V, M: Mode> HashMapAssertions<K, V> for AssertThat<'_, HashMap<K, V>, M> {
34    #[track_caller]
35    fn contains_key(self, expected: impl Borrow<K>) -> Self
36    where
37        K: Eq + Hash + Debug,
38        V: Debug,
39    {
40        self.track_assertion();
41
42        let expected = expected.borrow();
43
44        if !self.actual().contains_key(expected) {
45            self.fail(|w: &mut String| {
46                writedoc! {w, r#"
47                    Actual: HashMap {actual:#?}
48
49                    does not contain expected key: {expected:#?}
50                "#, actual = self.actual()}
51            });
52        }
53        self
54    }
55
56    #[track_caller]
57    fn does_not_contain_key(self, not_expected: impl Borrow<K>) -> Self
58    where
59        K: Eq + Hash + Debug,
60        V: Debug,
61    {
62        self.track_assertion();
63
64        let not_expected = not_expected.borrow();
65
66        if self.actual().contains_key(not_expected) {
67            self.fail(|w: &mut String| {
68                writedoc! {w, r#"
69                    Actual: HashMap {actual:#?}
70
71                    contains unexpected key: {not_expected:#?}
72                "#, actual = self.actual()}
73            });
74        }
75        self
76    }
77
78    #[track_caller]
79    fn contains_value<E>(self, expected: E) -> Self
80    where
81        K: Debug,
82        V: AssertrPartialEq<E> + Debug,
83        E: Debug,
84    {
85        self.track_assertion();
86
87        if !self
88            .actual()
89            .values()
90            .any(|it| AssertrPartialEq::eq(it, &expected, None))
91        {
92            self.fail(|w: &mut String| {
93                writedoc! {w, r#"
94                    Actual: HashMap {actual:#?}
95                    
96                    does not contain expected value: {expected:#?}
97                "#, actual = self.actual()}
98            });
99        }
100        self
101    }
102
103    #[track_caller]
104    fn contains_entry<E>(self, key: impl Borrow<K>, value: impl Borrow<E>) -> Self
105    where
106        K: Eq + Hash + Debug,
107        V: AssertrPartialEq<E> + Debug,
108        E: Debug,
109    {
110        let then = self.contains_key(key.borrow());
111
112        then.track_assertion();
113
114        let actual = then.actual();
115        let expected_key = key.borrow();
116        let expected_value = value.borrow();
117
118        match actual.get(expected_key) {
119            None => { /* Ignored: contains_key() already created an error in this case... */ }
120            Some(actual_value) => {
121                let mut ctx = EqContext::new();
122                if !AssertrPartialEq::eq(actual_value, expected_value, Some(&mut ctx)) {
123                    if !ctx.differences.differences.is_empty() {
124                        then.add_detail_message(format!("Differences: {:#?}", ctx.differences));
125                    }
126                    then.fail(|w: &mut String| {
127                        writedoc! {w, r#"
128                            Actual: HashMap {actual:#?}
129
130                            does not contain expected value at key: {expected_key:#?}
131
132                            Expected value: {expected_value:#?}
133                              Actual value: {actual_value:#?}
134                            "#,
135                        }
136                    });
137                }
138            }
139        }
140
141        then
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    mod contains_key {
148        use std::collections::HashMap;
149
150        use indoc::formatdoc;
151
152        use crate::prelude::*;
153
154        #[test]
155        fn succeeds_when_key_is_present() {
156            let mut map = HashMap::new();
157            map.insert("foo", "bar");
158            assert_that(map).contains_key("foo");
159        }
160
161        #[test]
162        fn panics_when_key_is_absent() {
163            assert_that_panic_by(|| {
164                let mut map = HashMap::new();
165                map.insert("foo", "bar");
166                assert_that(map).with_location(false).contains_key("baz");
167            })
168            .has_type::<String>()
169            .is_equal_to(formatdoc! {r#"
170                    -------- assertr --------
171                    Actual: HashMap {{
172                        "foo": "bar",
173                    }}
174
175                    does not contain expected key: "baz"
176                    -------- assertr --------
177                "#});
178        }
179    }
180
181    mod does_not_contain_key {
182        use std::collections::HashMap;
183
184        use indoc::formatdoc;
185
186        use crate::prelude::*;
187
188        #[test]
189        fn succeeds_when_key_is_absent() {
190            let mut map = HashMap::new();
191            map.insert("foo", "bar");
192            assert_that(map).does_not_contain_key("baz");
193        }
194
195        #[test]
196        fn panics_when_key_is_present() {
197            assert_that_panic_by(|| {
198                let mut map = HashMap::new();
199                map.insert("foo", "bar");
200                assert_that(map)
201                    .with_location(false)
202                    .does_not_contain_key("foo");
203            })
204            .has_type::<String>()
205            .is_equal_to(formatdoc! {r#"
206                    -------- assertr --------
207                    Actual: HashMap {{
208                        "foo": "bar",
209                    }}
210
211                    contains unexpected key: "foo"
212                    -------- assertr --------
213                "#});
214        }
215    }
216
217    mod contains_value {
218        use std::collections::HashMap;
219
220        use indoc::formatdoc;
221
222        use crate::prelude::*;
223
224        #[test]
225        fn succeeds_when_value_is_present() {
226            let mut map = HashMap::new();
227            map.insert("foo", "bar");
228            assert_that(map).contains_value("bar");
229        }
230
231        #[test]
232        fn panics_when_value_is_absent() {
233            assert_that_panic_by(|| {
234                let mut map = HashMap::new();
235                map.insert("foo", "bar");
236                assert_that(map).with_location(false).contains_value("baz");
237            })
238            .has_type::<String>()
239            .is_equal_to(formatdoc! {r#"
240                    -------- assertr --------
241                    Actual: HashMap {{
242                        "foo": "bar",
243                    }}
244
245                    does not contain expected value: "baz"
246                    -------- assertr --------
247                "#});
248        }
249
250        #[test]
251        fn compiles_with_any_type_comparable_to_the_actual_value_type() {
252            let mut map = HashMap::new();
253            map.insert("foo", "bar");
254            assert_that(map).contains_value("bar".to_string());
255        }
256    }
257
258    mod contains_entry {
259        use std::collections::HashMap;
260
261        use indoc::formatdoc;
262
263        use crate::prelude::*;
264
265        #[test]
266        fn succeeds_when_value_is_present() {
267            let mut map = HashMap::new();
268            map.insert("foo", "bar");
269            // TODO: Can we get rid of the requirement to explicitly define E as `&str` here?
270            assert_that(map).contains_entry::<&str>("foo", "bar");
271        }
272
273        #[test]
274        fn succeeds_when_value_is_present_with_complex_type_with_borrowable_values() {
275            #[derive(Debug, PartialEq)]
276            struct Person {
277                age: u32,
278            }
279            let mut map = HashMap::<&str, Person>::new();
280            map.insert("foo", Person { age: 42 });
281            assert_that_ref(&map).contains_entry("foo", &Person { age: 42 });
282            assert_that_ref(&map).contains_entry("foo", Person { age: 42 });
283            assert_that_ref(&map).contains_entry("foo", Box::new(Person { age: 42 }));
284        }
285
286        #[test]
287        fn panics_when_key_is_absent() {
288            assert_that_panic_by(|| {
289                let mut map = HashMap::new();
290                map.insert("foo", "bar");
291                assert_that(map)
292                    .with_location(false)
293                    .contains_entry::<&str>("baz", "someValue");
294            })
295            .has_type::<String>()
296            .is_equal_to(formatdoc! {r#"
297                    -------- assertr --------
298                    Actual: HashMap {{
299                        "foo": "bar",
300                    }}
301
302                    does not contain expected key: "baz"
303                    -------- assertr --------
304                "#});
305        }
306
307        #[test]
308        fn panics_when_key_is_present_but_value_is_not_equal() {
309            assert_that_panic_by(|| {
310                let mut map = HashMap::new();
311                map.insert("foo", "bar");
312                assert_that(map)
313                    .with_location(false)
314                    .contains_entry::<&str>("foo", "someValue");
315            })
316            .has_type::<String>()
317            .is_equal_to(formatdoc! {r#"
318                    -------- assertr --------
319                    Actual: HashMap {{
320                        "foo": "bar",
321                    }}
322
323                    does not contain expected value at key: "foo"
324
325                    Expected value: "someValue"
326                      Actual value: "bar"
327                    -------- assertr --------
328                "#});
329        }
330    }
331}