SpringBoot测试迷你系列 —— 二 使用WebMvcTest进行测试
使用WebMvcTest进行测试
原文Testing Web Controllers With Spring Boot @WebMvcTest
目录
- 单元测试
- 使用@WebMvcTest进行测试
- 使用@DataJpa进行持久层测试
- 使用@JsonTest测试序列化
- 使用MockWebServer测试Spring WebClient Rest调用
- 使用@SpringBootTest进行SpringBoot集成测试
非逐句翻译
单元测试足够了吗?
上一篇文章中说了使用Spring,我们应该如何进行单元测试。
但是系统中进行单元测试就足够了吗?
最起码单元测试不能做到这些
- Controller中的
@PostMapping
和@GetMapping
无法被处理,我们该如何知道Http请求带着@PathVariable
中的参数正确的映射到对应的Controller方法中了呢? - 如果Controller方法被
@RequestBody
和@ResponseBody
标注,我们如何检测输入被正确的反序列化,输出被正确的序列化? - 如果Controller参数被
@Vaild
标注,我们如何确定验证发生了? - 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"));
}
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());
}