Loading

SpringBoot测试迷你系列 —— 二 使用WebMvcTest进行测试

使用WebMvcTest进行测试

原文Testing Web Controllers With Spring Boot @WebMvcTest

目录

  1. 单元测试
  2. 使用@WebMvcTest进行测试
  3. 使用@DataJpa进行持久层测试
  4. 使用@JsonTest测试序列化
  5. 使用MockWebServer测试Spring WebClient Rest调用
  6. 使用@SpringBootTest进行SpringBoot集成测试

非逐句翻译

单元测试足够了吗?

上一篇文章中说了使用Spring,我们应该如何进行单元测试。

但是系统中进行单元测试就足够了吗?

最起码单元测试不能做到这些

  1. Controller中的@PostMapping@GetMapping无法被处理,我们该如何知道Http请求带着@PathVariable中的参数正确的映射到对应的Controller方法中了呢?
  2. 如果Controller方法被@RequestBody@ResponseBody标注,我们如何检测输入被正确的反序列化,输出被正确的序列化?
  3. 如果Controller参数被@Vaild标注,我们如何确定验证发生了?
  4. Spring中的Controller异常时会走ExceptionHandler,我们只能对一个方法进行单元测试,如何保证ExceptionHandler被正确调用。

使用@WebMvcTest编写集成测试

上面的种种问题都说明仅仅是单元测试满足不了我们的测试需求,它只能保证开发过程中快速的验证方法的逻辑正确性。现在我们需要编写集成测试了。

现在,我们想测试应用的Web层行为是否正确,并且,像前面说的,我们不想卷入数据库调用。

想要测试SpringMVC Controller,我们可以使用@WebMvcTest注解,它只扫描@Controller@ControllerAdvice标注的Bean,还有一些其他和Web层相关的Bean。

这是一个基础的示例

@WebMvcTest(BookController.class)
class BooklistApplicationTests {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookRepository repository;

    @Test
    public void testReadingList() throws Exception { }
}

因为我们不想卷入数据库层的调用,所以我们把BookRepository标注了@MockBean注解,作为一个mock对象(伪造对象)。并且因为@WebMvcTest不扫描Controller之外的Bean,所以我们要提供所有Controller依赖的Bean对象。

如果我们不传递一个Controller类给@WebMvcTest,那么Spring将扫描所有的控制器,而且我们必须mock(伪造)所有的控制器依赖的bean。

由于@WebMvcTest标注了@AutoConfigureMockMvc,所以我们可以直接注入一个MockMvc对象,它用来伪造HTTP请求,让我们无需真的启动一个服务器就能通过伪造的请求来进行Controller测试。

验证HTTP请求和返回正确性

@Test
public void testReadingList() throws Exception {
    assertNotNull(mockMvc);
    List<Book> books = new ArrayList<>();
    when(repository.findBooksByReader(any())).thenReturn(books);

    mockMvc.perform(get("/readinglist/yulaoba"))
            .andExpect(status().isOk())
            .andExpect(view().name("readingList"))
            .andExpect(model().attributeExists("books"))
            .andExpect(model().attribute("books", is(empty())));
}

这里使用了perform发送了一个模拟的get请求,并且判断了请求的返回状态,视图名,模型参数。

结果序列化的校验

@Test
public void testReadingList2() throws Exception{
    assertNotNull(mockMvc);
    when(repository.findBooksByReader(any())).thenReturn(Arrays.asList(
            new Book(1l, "yulaoba", "123123123123123", "JAVA", "Smith", "asdfafd")
    ));
    mockMvc.perform(get("/readinglist/api/{reader}", "yulaoba"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON))
            .andExpect(content().json("[{'id': 1, 'reader': 'yulaoba', 'isbn': '123123123123123', 'title': 'JAVA', 'author': 'Smith', 'description': 'asdfafd'}]"))
            .andExpect(jsonPath("$[0]['title']").value("JAVA"));
}

jsonPath详细用法

ExceptionHandler的测试

假设我们的Controller中有这样的@ExceptionHandler

@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ResponseBody
public String handleException() {
    return "METHOD_NOT_ALLOWED";
}

然后先假装让repository抛出异常,然后使用status对其返回状态码进行校验。

@Test
public void testExceptionHandler() throws Exception {
    when(repository.findBooksByReader(any())).thenThrow(RuntimeException.class);

    mockMvc.perform(get("/readinglist/api/{reader}", "yulaoba"))
            .andExpect(status().isMethodNotAllowed());
}
posted @ 2021-10-11 09:29  yudoge  阅读(3080)  评论(0编辑  收藏  举报