expect_json/expect/ops/expect_object/
expect_object.rs

1use crate::expect::ops::expect_object::ExpectObjectSubOp;
2use crate::expect_core::expect_op;
3use crate::expect_core::Context;
4use crate::expect_core::ExpectOp;
5use crate::expect_core::ExpectOpResult;
6use crate::JsonType;
7use serde_json::Map;
8use serde_json::Value;
9
10#[expect_op(internal, name = "object")]
11#[derive(Debug, Clone, Default, PartialEq)]
12pub struct ExpectObject {
13    sub_ops: Vec<ExpectObjectSubOp>,
14}
15
16impl ExpectObject {
17    pub(crate) fn new() -> Self {
18        Self { sub_ops: vec![] }
19    }
20
21    pub fn empty(mut self) -> Self {
22        self.sub_ops.push(ExpectObjectSubOp::Empty);
23        self
24    }
25
26    pub fn not_empty(mut self) -> Self {
27        self.sub_ops.push(ExpectObjectSubOp::NotEmpty);
28        self
29    }
30
31    pub fn contains<V>(mut self, expected_values: V) -> Self
32    where
33        V: Into<Value>,
34    {
35        let value = Into::<Value>::into(expected_values);
36        let sub_op = match value {
37            Value::Object(values_object) => ExpectObjectSubOp::Contains(values_object),
38            _ => {
39                let value_type = JsonType::from(&value);
40                panic!("object().contains() expected to take object. Received: {value_type}");
41            }
42        };
43
44        self.sub_ops.push(sub_op);
45        self
46    }
47}
48
49impl ExpectOp for ExpectObject {
50    fn on_object(
51        &self,
52        context: &mut Context,
53        received: &Map<String, Value>,
54    ) -> ExpectOpResult<()> {
55        for sub_op in &self.sub_ops {
56            sub_op.on_object(self, context, received)?;
57        }
58
59        Ok(())
60    }
61
62    fn debug_supported_types(&self) -> &'static [JsonType] {
63        &[JsonType::Object]
64    }
65}
66
67#[cfg(test)]
68mod test_contains {
69    use crate::expect;
70    use crate::expect_json_eq;
71    use pretty_assertions::assert_eq;
72    use serde_json::json;
73
74    #[test]
75    fn it_should_be_equal_for_identical_objects() {
76        let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
77        let right =
78            json!(expect::object()
79                .contains(json!({ "name": "John", "age": 30, "scores": [1, 2, 3] })));
80
81        let output = expect_json_eq(&left, &right);
82        assert!(output.is_ok());
83    }
84
85    #[test]
86    fn it_should_be_equal_for_reversed_identical_objects() {
87        let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
88        let right =
89            json!(expect::object()
90                .contains(json!({ "scores": [1, 2, 3], "age": 30, "name": "John" })));
91
92        let output = expect_json_eq(&left, &right);
93        assert!(output.is_ok());
94    }
95
96    #[test]
97    fn it_should_be_equal_for_partial_contains() {
98        let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
99        let right = json!(expect::object().contains(json!({ "name": "John", "age": 30 })));
100
101        let output = expect_json_eq(&left, &right);
102        assert!(output.is_ok());
103    }
104
105    #[test]
106    fn it_should_be_equal_for_nested_contains() {
107        let left = json!({ "name": "John", "comments": [
108            {
109                "text": "Hello",
110                "author": {
111                    "name": "Jane",
112                    "age": 25
113                }
114            },
115            {
116                "text": "Goodbye",
117                "author": {
118                    "name": "John",
119                    "age": 30
120                }
121            }
122        ]});
123
124        let right = json!(expect::object().contains(
125            json!({ "comments": expect::array().contains([
126                json!({
127                    "text": "Hello",
128                    "author": expect::object().contains(
129                        json!({
130                            "name": "Jane",
131                        })
132                    )
133                }),
134            ])})
135        ));
136
137        let output = expect_json_eq(&left, &right);
138        assert!(output.is_ok(), "{}", output.unwrap_err().to_string());
139    }
140
141    #[test]
142    fn it_should_error_for_same_fields_but_different_values() {
143        let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
144        let right = json!(
145            expect::object().contains(json!({ "name": "Joe", "age": 31, "scores": [4, 5, 6] }))
146        );
147
148        let output = expect_json_eq(&left, &right).unwrap_err().to_string();
149        assert_eq!(
150            output,
151            r#"Json integers at root.age are not equal:
152    expected 31
153    received 30"#
154        );
155    }
156
157    #[test]
158    fn it_should_be_ok_for_empty_contains() {
159        let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
160        let right = json!(expect::object().contains(json!({})));
161
162        let output = expect_json_eq(&left, &right);
163        assert!(output.is_ok());
164    }
165
166    #[test]
167    fn it_should_be_ok_for_empty_on_empty_object() {
168        let left = json!({});
169        let right = json!(expect::object().contains(json!({})));
170
171        let output = expect_json_eq(&left, &right);
172        assert!(output.is_ok());
173    }
174
175    #[test]
176    fn it_should_error_if_used_against_the_wrong_type() {
177        let left = json!("🦊");
178        let right = json!(expect::object().contains(json!({})));
179
180        let output = expect_json_eq(&left, &right).unwrap_err().to_string();
181        assert_eq!(
182            output,
183            r#"Json expect::object() at root, received wrong type:
184    expected object
185    received string "🦊""#
186        );
187    }
188
189    #[test]
190    fn it_should_error_for_nested_contains_via_array_on_differences() {
191        let left = json!({ "name": "John", "comments": [
192            {
193                "text": "Hello",
194                "author": {
195                    "name": "🦊",
196                    "age": 25
197                }
198            },
199            {
200                "text": "Goodbye",
201                "author": {
202                    "name": "John",
203                    "age": 30
204                }
205            }
206        ]});
207
208        let right = json!(expect::object().contains(
209            json!({ "comments": expect::array().contains([
210                json!({
211                    "text": "Hello",
212                    "author": expect::object().contains(
213                        json!({
214                            "name": "Jane",
215                        })
216                    )
217                }),
218            ])})
219        ));
220
221        let output = expect_json_eq(&left, &right).unwrap_err().to_string();
222        assert_eq!(
223            output,
224            r#"Json array at root.comments does not contain expected value:
225    expected array to contain {
226        "author": expect::object(),
227        "text": "Hello"
228    }, but it was not found.
229    received [
230        {
231            "author": {
232                "age": 25,
233                "name": "🦊"
234            },
235            "text": "Hello"
236        },
237        {
238            "author": {
239                "age": 30,
240                "name": "John"
241            },
242            "text": "Goodbye"
243        }
244    ]"#
245        );
246    }
247
248    #[test]
249    fn it_should_error_for_nested_contains_via_object_with_inner_contains_error() {
250        let left = json!({
251            "name": "John",
252            "comment": {
253                "text": "Hello",
254                "author": {
255                    "name": "🦊",
256                    "age": 25
257                }
258            },
259        });
260
261        let right = json!(expect::object().contains(json!({ "comment":
262            expect::object().contains(
263                json!({
264                    "text": "Hello",
265                    "author": expect::object().contains(
266                        json!({
267                            "name": "Jane",
268                        })
269                    )
270                })
271            )
272        })));
273
274        let output = expect_json_eq(&left, &right).unwrap_err().to_string();
275        assert_eq!(
276            output,
277            r#"Json strings at root.comment.author.name are not equal:
278    expected "Jane"
279    received "🦊""#
280        );
281    }
282
283    // TODO, is this correct?
284    // The error message looks like it is checking the key against an expect op.
285    #[test]
286    fn it_should_error_for_nested_contains_via_different_object_with_inner_contains_error() {
287        let left = json!({
288            "name": "John",
289            "comment": {
290                "text": "Hello",
291                "author": {
292                    "name": "Jane",
293                    "age": 25
294                }
295            },
296        });
297
298        let right = json!(expect::object().contains(json!({ "comment":
299            expect::object().contains(
300                json!({
301                    "text": "Hello",
302                    "author": expect::object().contains(
303                        json!({
304                            "something_else": "🦊",
305                        })
306                    )
307                })
308            )
309        })));
310
311        let output = expect_json_eq(&left, &right).unwrap_err().to_string();
312        assert_eq!(
313            output,
314            r#"Json object at root.comment.author is missing key for object:
315    expected field 'something_else',
316    but it was not found"#
317        );
318    }
319}
320
321#[cfg(test)]
322mod test_empty {
323    use crate::expect;
324    use crate::expect_json_eq;
325    use pretty_assertions::assert_eq;
326    use serde_json::json;
327
328    #[test]
329    fn it_should_pass_when_object_is_empty() {
330        let left = json!({});
331        let right = json!(expect::object().empty());
332
333        let output = expect_json_eq(&left, &right);
334        assert!(output.is_ok(), "assertion error: {output:#?}");
335    }
336
337    #[test]
338    fn it_should_fail_when_object_is_not_empty() {
339        let left = json!({ "foo": "bar" });
340        let right = json!(expect::object().empty());
341
342        let output = expect_json_eq(&left, &right).unwrap_err().to_string();
343        assert_eq!(
344            output,
345            r#"Json expect::object() error at root:
346    expected empty object
347    received {
348        "foo": "bar"
349    }"#
350        );
351    }
352}
353
354#[cfg(test)]
355mod test_not_empty {
356    use crate::expect;
357    use crate::expect_json_eq;
358    use pretty_assertions::assert_eq;
359    use serde_json::json;
360
361    #[test]
362    fn it_should_pass_when_object_is_not_empty() {
363        let left = json!({ "foo": "bar" });
364        let right = json!(expect::object().not_empty());
365
366        let output = expect_json_eq(&left, &right);
367        assert!(output.is_ok(), "assertion error: {output:#?}");
368    }
369
370    #[test]
371    fn it_should_fail_when_object_is_empty() {
372        let left = json!({});
373        let right = json!(expect::object().not_empty());
374
375        let output = expect_json_eq(&left, &right).unwrap_err().to_string();
376        assert_eq!(
377            output,
378            r#"Json expect::object() error at root:
379    expected non-empty object
380    received {}"#
381        );
382    }
383}