如何在 Spring Boot 中测试 json 结构

12 testing json mocking mockito 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"));
    }

}
Run Code Online (Sandbox Code Playgroud)

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

use*_*710 13

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

例如,假设您有一个@Controller(或@RestController) 访问 a @Repository(直接或通过 a 间接访问@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() { ... }

}
Run Code Online (Sandbox Code Playgroud)
@Repository
public class MyRepository {

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

}
Run Code Online (Sandbox Code Playgroud)
@RestController
@RequestMapping("/")
public class MyController {

    @Autowired
    private MyRepository repository;

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

}
Run Code Online (Sandbox Code Playgroud)

那么GET请求?id=1可能会返回如下内容:

{
    "id": 1,
    "name": "John",
    "age": 12
}
Run Code Online (Sandbox Code Playgroud)

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

2. 通过jsonPath()

在这种情况下,您可以使用jsonPath(一种计算结构上表达式的方法JSON,类似于XPathfor 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);
    }
Run Code Online (Sandbox Code Playgroud)

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

.andExpect(jsonPath("$[0].name", is("Mary")))
Run Code Online (Sandbox Code Playgroud)

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

.andExpect(jsonPath("$.serverGeneratedSecretKey", notNullValue()))
Run Code Online (Sandbox Code Playgroud)

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);

Run Code Online (Sandbox Code Playgroud)

然而,这种方式非常善变:

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

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

有趣的是,Java13终于支持文本块了。这些块虽然可以更轻松地编写预期的响应字符串,但对其他方面没有帮助。