如何在 spring 引导中测试 json 结构

How to test json structure in spring boot

我找到这段代码来测试 json 字符串是否等于另一个字符串

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcExampleTests {

    @Autowired
    private MockMvc mvc;

    @Test
    public void exampleTest() throws Exception {
        this.mvc.perform(get("/")).andExpect(status().isOk())
                .andExpect(content().string("Hello World"));
    }

}

你知道如何测试 json 结构吗?比如检查对象是否包含 id、name、age ... 无论值是什么。 谢谢

验证正确 JSON 响应的最佳方式取决于您的响应。

例如,假设您有一个 @Controller(或 @RestController)访问 @Repository(直接或间接通过 @Service)和映射结果项目 JSON。例如:

public class MyPojo {

    private int id;
    private String name;
    private int age;

    // no-args for deserialization, required by some Frameworks with default settings
    public MyPojo() {}

    // constructor
    public MyPojo(final int id, final String name, final int age) { ... }
    
    // Getters
    public int getid() { ... }
    public String getName() { ... }
    public int getName() { ... }

}
@Repository
public class MyRepository {

    public MyPojo findMyPojoById(final int id) { return db.findById(id); }

}
@RestController
@RequestMapping("/")
public class MyController {

    @Autowired
    private MyRepository repository;

    @GetMapping
    public MyPojo findMyPojoById(@RequestParam final int id) {
        return repository.findMyPojoByid(id);
    }

}

然后带有 ?id=1GET 请求可能会 return 类似于以下内容:

{
    "id": 1,
    "name": "John",
    "age": 12
}

在这种情况下,您有多种选择。在 mockMvc 测试中,您通常对测试组件的集成不感兴趣,只想测试您的控制器是否按预期工作。在这种情况下,您可能需要 unit-test 并模拟存储库。您有一些选择:

1。通过模拟的预期响应

在这种情况下,您可以执行以下操作:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcExampleTests {

    @Autowired
    private MockMvc mvc;
    
    @MockBean
    private MyRepository repository; // mock the repository
   
    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void exampleTest() throws Exception {
        final int userId = 1;
        MyPojo mockedUser = new MyPojo(userId, "John", 12);
        Mockito.doReturn(mockedUser).when(repository).findMyPojoById(userId);
        
        final String expectedResponseContent = objectMapper.writeValueAsString(mockedUser);
 
        this.mvc.perform(get("/"))
            .andExpect(status().isOk())
            .andExpect(content().json(expectedResponseContent ));
    
        verify(repository).findMyPojoById(userId); // verify that the repository was called correctly
    }
}

这样做的好处是测试更稳健。如果您的 MyPojo 对象发生变化,只要您不更新测试中模拟对象的所有构造函数用法,您的测试就会失败 - 这是一个有争议的问题,因为代码不会编译,直到您这样做。

2。通过 jsonPath()

在这种情况下,您可以使用 jsonPath(一种计算 JSON 结构上的表达式的方法,类似于 XPath 用于 XML)来单独计算某些或JSON 结构的所有部分。

例如(仍然嘲笑响应):

    @Test
    public void exampleTest() throws Exception {
        final int userId = 1;
        MyPojo mockedUser = new MyPojo(userId, "John", 12);
        Mockito.doReturn(mockedUser).when(repository).findMyPojoById(userId); // note that this mock is not necessary, but it does make the test a unit test
         
        this.mvc.perform(get("/"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id", is(userId))) // is(<Object>) is e.g. a Hamcrest Matcher
            .andExpect(jsonPath("$.name", is("John"))
            .andExpect(jsonPath("$.age", is(12)))

        verify(repository).findMyPojoById(userId);
    }

请注意,如果您有一组响应对象,您可以使用适当的数组表示法解析它们,例如:

.andExpect(jsonPath("$[0].name", is("Mary")))

这样做的好处是您不必评估整个响应。这可能是有用的,例如,如果服务器响应包含服务器生成的数据,这些数据很难复制或模拟,而不会消除太多实际代码逻辑(通过例如模拟)。在这种情况下,您可以检查字段的 presence,而不考虑其实际值,例如:

.andExpect(jsonPath("$.serverGeneratedSecretKey", notNullValue()))

3。通过字符串匹配

最后,你可以比较实际的反应:


String expectedResponse = "{\"id\": 1, \"name\":\"John\", \"age\": 12}";

String responseString = mockMvc.perform(...) 
                               .andExpect(status().isOk())
                               .andReturn()
                               .getResponse()
                               .getContentAsString();

assertEquals("Response does not match", expectedResponse, responseString);

但是,这种方式很善变:

  • 您需要完全匹配 JSON ,其中包括转义 " 和适当的空格。
  • 虽然例如ObjectMapper 尝试遵循 JSON 字段出现的顺序,你不应该让你的代码基于 JSON 对象的顺序工作(除了数组,在 JSON 未定义)
  • 这种方法也使重构成为一场噩梦。添加字段会破坏您的测试,使您不得不更改预期的字符串 - 由于上述原因容易出错。

我想不出这种方法的一个很好的用例,但是,好吧,如果你想要的话,它就在那里。

值得注意的是 Java 13 终于支持 text blocks。这些块虽然可以更轻松地编写预期的响应字符串,但对其他方面没有帮助。