Skip to main content

googletest/matchers/
has_entry_matcher.rs

1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::description::Description;
16use crate::matcher::{Matcher, MatcherBase, MatcherResult};
17use std::collections::HashMap;
18use std::fmt::Debug;
19use std::hash::Hash;
20
21/// Matches a `&HashMap` containing the given `key` whose value is matched by
22/// the matcher `inner`.
23///
24/// ```
25/// # use googletest::prelude::*;
26/// # use std::collections::HashMap;
27/// # fn should_pass() -> Result<()> {
28/// let value = HashMap::from([(0, 1), (1, -1)]);
29/// verify_that!(value, has_entry(0, eq(&1)))?;  // Passes
30/// #     Ok(())
31/// # }
32/// # fn should_fail_1() -> Result<()> {
33/// # let value = HashMap::from([(0, 1), (1, -1)]);
34/// verify_that!(value, has_entry(1, gt(&0)))?;  // Fails: value not matched
35/// #     Ok(())
36/// # }
37/// # fn should_fail_2() -> Result<()> {
38/// # let value = HashMap::from([(0, 1), (1, -1)]);
39/// verify_that!(value, has_entry(2, eq(&0)))?;  // Fails: key not present
40/// #     Ok(())
41/// # }
42/// # should_pass().unwrap();
43/// # should_fail_1().unwrap_err();
44/// # should_fail_2().unwrap_err();
45/// ```
46///
47/// Note: One could obtain the same effect by using `contains` and a
48/// `Matcher<(&Key, &Value)>`:
49///
50/// ```
51/// # use googletest::prelude::*;
52/// # use std::collections::HashMap;
53/// # fn should_pass() -> Result<()> {
54/// let value = HashMap::from([(0, 1), (1, -1)]);
55/// verify_that!(value, contains(eq((&0, &1))))?;
56/// #     Ok(())
57/// # }
58/// # should_pass().unwrap();
59/// ```
60pub fn has_entry<KeyT, MatcherT>(key: KeyT, inner: MatcherT) -> HasEntryMatcher<KeyT, MatcherT> {
61    HasEntryMatcher { key, inner }
62}
63
64#[derive(MatcherBase)]
65pub struct HasEntryMatcher<KeyT, MatcherT> {
66    key: KeyT,
67    inner: MatcherT,
68}
69
70impl<'a, KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<&'a ValueT>>
71    Matcher<&'a HashMap<KeyT, ValueT>> for HasEntryMatcher<KeyT, MatcherT>
72{
73    fn matches(&self, actual: &'a HashMap<KeyT, ValueT>) -> MatcherResult {
74        if let Some(value) = actual.get(&self.key) {
75            self.inner.matches(value)
76        } else {
77            MatcherResult::NoMatch
78        }
79    }
80
81    fn explain_match(&self, actual: &'a HashMap<KeyT, ValueT>) -> Description {
82        if let Some(value) = actual.get(&self.key) {
83            format!(
84                "which contains key {:?}, but is mapped to value {:#?}, {}",
85                self.key,
86                value,
87                self.inner.explain_match(value)
88            )
89            .into()
90        } else {
91            format!("which doesn't contain key {:?}", self.key).into()
92        }
93    }
94
95    fn describe(&self, matcher_result: MatcherResult) -> Description {
96        match matcher_result {
97            MatcherResult::Match => format!(
98                "contains key {:?}, which value {}",
99                self.key,
100                self.inner.describe(MatcherResult::Match)
101            )
102            .into(),
103            MatcherResult::NoMatch => format!(
104                "doesn't contain key {:?} or contains key {:?}, which value {}",
105                self.key,
106                self.key,
107                self.inner.describe(MatcherResult::NoMatch)
108            )
109            .into(),
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use crate::prelude::*;
117    use crate::Result;
118    use indoc::indoc;
119    use std::collections::HashMap;
120
121    #[test]
122    fn has_entry_does_not_match_empty_hash_map() -> Result<()> {
123        let value: HashMap<i32, i32> = HashMap::new();
124        verify_that!(&value, not(has_entry(0, eq(&0))))
125    }
126
127    #[test]
128    fn has_entry_matches_hash_map_with_value() -> Result<()> {
129        let value: HashMap<i32, i32> = HashMap::from([(0, 0)]);
130        verify_that!(&value, has_entry(0, eq(&0)))
131    }
132
133    #[test]
134    fn has_entry_does_not_match_hash_map_with_wrong_value() -> Result<()> {
135        let value: HashMap<i32, i32> = HashMap::from([(0, 1)]);
136        verify_that!(&value, not(has_entry(0, eq(&0))))
137    }
138
139    #[test]
140    fn has_entry_does_not_match_hash_map_with_wrong_key() -> Result<()> {
141        let value: HashMap<i32, i32> = HashMap::from([(1, 0)]);
142        verify_that!(&value, not(has_entry(0, eq(&0))))
143    }
144
145    #[test]
146    fn has_entry_shows_correct_message_when_key_is_not_present() -> Result<()> {
147        let result = verify_that!(HashMap::from([(0, 0)]), has_entry(1, eq(&0)));
148
149        verify_that!(
150            result,
151            err(displays_as(contains_substring(indoc!(
152                "
153                Value of: HashMap::from([(0, 0)])
154                Expected: contains key 1, which value is equal to 0
155                Actual: {0: 0},
156                  which doesn't contain key 1
157                "
158            ))))
159        )
160    }
161
162    #[test]
163    fn has_entry_shows_correct_message_when_key_has_non_matching_value() -> Result<()> {
164        let result = verify_that!(HashMap::from([(0, 0)]), has_entry(0, eq(&1)));
165
166        verify_that!(
167            result,
168            err(displays_as(contains_substring(indoc!(
169                "
170                Value of: HashMap::from([(0, 0)])
171                Expected: contains key 0, which value is equal to 1
172                Actual: {0: 0},
173                  which contains key 0, but is mapped to value 0, which isn't equal to 1
174                "
175            ))))
176        )
177    }
178}