Spring MockMvc:以任意顺序匹配JSON对象的集合

时间:2019-03-20 19:45:39

标签: java spring spring-mvc hamcrest mockmvc

我有一个API端点,当使用GET调用该端点时,将返回体内的JSON对象数组,如下所示:

[
  {"id": "321", "created": "2019-03-01", "updated": "2019-03-15"},
  {"id": "123", "created": "2019-03-02", "updated": "2019-03-16"}
]

我想用Spring MockMvc测试用例检查主体。 该语句当前如下所示:

mockMvc.perform(get("/myapi/v1/goodstuff").
  andExpect(status().isOk()).
  andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)).
  andExpect(jsonPath("$.*", isA(ArrayList.class))).
  andExpect(jsonPath("$.*", hasSize(2))).
  andExpect(jsonPath("$[0].id", is("321"))).
  andExpect(jsonPath("$[0].created", is("2019-03-01"))).
  andExpect(jsonPath("$[0].updated*", is("2019-03-15"))).
  andExpect(jsonPath("$[1].id", is("1232"))).
  andExpect(jsonPath("$[1].created", is("2019-03-02"))).
  andExpect(jsonPath("$[1].updated*", is("2019-03-16")));

但是,我的API的实现不能保证返回数组中JSON对象的顺序。 如果这是一个字符串数组,我可以通过org.hamcrest.collection.IsIterableContainingInAnyOrder<T>.containsInAnyOrder生成的匹配器来解决。 但是我找不到适合我的情况in their doc的匹配器,也看不到jsonPath方法in Spring docs

的描述中的任何线索。

通过快速搜索,我也没有找到与我的情况有关的任何信息,除了我上面描述的list of strings situation之外。 当然,我可以将JSON对象转换为字符串。

但是我想知道,我能否为JSON对象列表解决此问题,将每个对象的每个字段一一比较(如上面的代码片段所示),但是忽略了对象的顺序在收藏中吗?

更新Zgurskyi has suggested一种可以帮助我简化原始示例的解决方案。但是,在一个实际的实际示例中,还有2个输入:

  • 字段数是10-20,而不是3
  • 并非所有匹配项都是纯is,例如:

(更接近我的原始代码)

mockMvc.perform(get("/myapi/v1/greatstuff").
      andExpect(status().isOk()).
      andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)).
      andExpect(jsonPath("$.*", isA(ArrayList.class))).
      andExpect(jsonPath("$.*", hasSize(2))).
      andExpect(jsonPath("$[0].id", is("321"))).
      andExpect(jsonPath("$[0].did", anything())).
      andExpect(jsonPath("$[0].createdTs", startsWith("2019-03-01"))).
      andExpect(jsonPath("$[0].updatedTs", startsWith("2019-03-15"))).
      andExpect(jsonPath("$[0].name", equalToIgnoringCase("wat"))).
      andExpect(jsonPath("$[0].stringValues", containsInAnyOrder("a","b","c"))).
      andExpect(jsonPath("$[1].id", is("1232"))).
      andExpect(jsonPath("$[1].did", anything())).
      andExpect(jsonPath("$[1].createdTs", startsWith("2019-03-01"))).
      andExpect(jsonPath("$[1].updatedTs", startsWith("2019-03-15"))).
      andExpect(jsonPath("$[1].name", equalToIgnoringCase("taw"))).
      andExpect(jsonPath("$[1].stringValues", containsInAnyOrder("d","e","f"))).
      andReturn();

到目前为止,似乎没有什么比实现自己的matcher类更好的了。

或者...我可以吗?

2 个答案:

答案 0 :(得分:2)

您可以断言列表项字段而忽略顺序:

.andExpect(jsonPath("$[*].id", containsInAnyOrder("321", "123")))
.andExpect(jsonPath("$[*].created", containsInAnyOrder("2019-03-01", "2019-03-02")))
.andExpect(jsonPath("$[*].updated", containsInAnyOrder("2019-03-15", "2019-03-16")))

另一种方法是检查响应中是否存在特定的列表项:

.andExpect(jsonPath("$.[?(@.id == 123 && @.created == \"2019-03-02\" && @.updated == \"2019-03-16\")]").exists())
.andExpect(jsonPath("$.[?(@.id == 321 && @.created == \"2019-03-01\" && @.updated == \"2019-03-15\")]").exists())

答案 1 :(得分:2)

另外,还有一种方法可以使用MockMvcResultMatchers来声明JSON,而不必严格按顺序进行操作

.andExpect(MockMvcResultMatchers.content().json(<json-here>, false))

通过设置strict = false,它可以进行挑剔的搜索。