expect_json/expects/ops/expect_object/
expect_object.rs

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