1. 程式人生 > 其它 >SpringBoot測試迷你係列 —— 二 使用WebMvcTest進行測試

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