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