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