單元測試不止Junit,會MockMvc才是高手!

  • 2019 年 10 月 4 日
  • 筆記
  • 作者:AlanShelby(同公眾號)
  • https://zhuanlan.zhihu.com/p/43260823

一、前言

在前面的章節我們介紹過 Junit 的使用,也了解過 spring-test,今天我們來了解一個新玩意 — mock 測試。這裡僅僅做一個入門,對返回視圖和返回 Json 數據的方法進行測試演示,不會把所有的方法都介紹到,具體文檔詳見鏈接:Mock Test,本章節主要講解以下兩部分內容:

1、Mock 測試簡介

2、測試用例演示

二、Mock 測試簡介

1、什麼是 mock 測試

在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法,就是 mock 測試在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法,就是* mock 測試*。

  • 虛擬的對象就是 mock 對象。
  • mock 對象就是真實對象在調試期間的代替品。

2、為什麼使用 mock 測試

  • 避免開發模塊之間的耦合
  • 輕量、簡單、靈活

3、MockMVC 介紹

基於 RESTful 風格的 SpringMVC 的測試,我們可以測試完整的 Spring MVC 流程,即從 URL 請求到控制器處理,再到視圖渲染都可以測試。

1)MockMvcBuilder

MockMvcBuilder 是用來構造 MockMvc 的構造器,其主要有兩個實現:StandaloneMockMvcBuilder 和 DefaultMockMvcBuilder,對於我們來說直接使用靜態工廠 MockMvcBuilders 創建即可。

2)MockMvcBuilders

負責創建 MockMvcBuilder 對象,有兩種創建方式:

standaloneSetup(Object… controllers):通過參數指定一組控制器,這樣就不需要從上下文獲取了。

webAppContextSetup(WebApplicationContext wac):指定 WebApplicationContext,將會從該上下文獲取相應的控制器並得到相應的 MockMvc,本章節下面測試用例均使用這種方式創建 MockMvcBuilder 對象。

3)MockMvc

對於服務器端的 SpringMVC 測試支持主入口點。通過 MockMvcBuilder 構造 MockMvcBuilder 由 MockMvcBuilders 建造者的靜態方法去建造。

核心方法:perform(RequestBuilder rb) — 執行一個 RequestBuilder 請求,會自動執行 SpringMVC 的流程並映射到相應的控制器執行處理,該方法的返回值是一個 ResultActions。

4)ResultActions

(1)andExpect:添加 ResultMatcher 驗證規則,驗證控制器執行完成後結果是否正確;

(2)andDo:添加 ResultHandler 結果處理器,比如調試時打印結果到控制台;

(3)andReturn:最後返回相應的 MvcResult;然後進行自定義驗證 / 進行下一步的異步處理;

5)MockMvcRequestBuilders

用來構建請求的,其主要有兩個子類 MockHttpServletRequestBuilder *和 MockMultipartHttpServletRequestBuilder*(如文件上傳使用),即用來 Mock 客戶端請求需要的所有數據。

6)MockMvcResultMatchers

(1)用來匹配執行完請求後的結果驗證

(2)如果匹配失敗將拋出相應的異常

(3)包含了很多驗證 API 方法

7)MockMvcResultHandlers

(1)結果處理器,表示要對結果做點什麼事情

(2)比如此處使用 MockMvcResultHandlers.print() 輸出整個響應結果信息

8)MvcResult

(1)單元測試執行結果,可以針對執行結果進行自定義驗證邏輯

三、測試用例演示

1、添加依賴

<!-- spring 單元測試組件包 -->  <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-test</artifactId>      <version>5.0.7.RELEASE</version>  </dependency>  <!-- 單元測試Junit -->  <dependency>      <groupId>junit</groupId>      <artifactId>junit</artifactId>      <version>4.12</version>  </dependency>  <!-- Mock測試使用的json-path依賴 -->  <dependency>      <groupId>com.jayway.jsonpath</groupId>      <artifactId>json-path</artifactId>      <version>2.2.0</version>  </dependency>

前兩個 jar 依賴我們都已經接觸過了,對於返回視圖方法的測試這兩個 jar 依賴已經足夠了,第三個 jar 依賴是用於處理返回 Json 數據方法的,這裡要明白每個 jar 的具體作用。

2、被測試的方法

@RequestMapping(value = "editItem")  public String editItem(Integer id, Model model) {      Item item = itemService.getItemById(id);      model.addAttribute("item", item);      return "itemEdit";  }    @RequestMapping(value = "getItem")  @ResponseBody  public Item getItem(Integer id) {      Item item = itemService.getItemById(id);      return item;  }

這裡我們提供了兩個方法,一個是返回視圖的方法,另一個是返回 Json 數據的方法,下面我們會給出測試類,分別對這兩個方法進行測試。

3、測試類:ItemMockTest

@RunWith(SpringJUnit4ClassRunner.class)  @ContextConfiguration(locations = "classpath:spring/*.xml")  @WebAppConfiguration  public class ItemMockTest {        @Autowired      private WebApplicationContext context;        private MockMvc mockMvc;        @Before      public void init() {          mockMvc = MockMvcBuilders.webAppContextSetup(context).build();      }  }

這裡前兩個註解就不再解釋了,我們在學習 Spring 與 Junit 整合的時候已經講解過了,這裡說一下第三個註解:@WebAppConfiguration:可以在單元測試的時候,不用啟動 Servlet 容器,就可以獲取一個 Web 應用上下文。

1)返回視圖方法測試

@Test  public void test() throws Exception {      MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/editItem").param("id", "1"))              .andExpect(MockMvcResultMatchers.view().name("itemEdit"))              .andExpect(MockMvcResultMatchers.status().isOk())              .andDo(MockMvcResultHandlers.print())              .andReturn();      Assert.assertNotNull(result.getModelAndView().getModel().get("item"));  }

這三句代碼是我們對結果的期望,最後打印出了結果,說明執行成功,所有期望都達到了,否則會直接報錯。從結果中我們就可以看到這個請求測試的情況。

2、返回 Json 數據方法

@Test  public void test1() throws Exception {      mockMvc.perform(MockMvcRequestBuilders.get("/getItem")              .param("id", "1")              .accept(MediaType.APPLICATION_JSON))              .andExpect(MockMvcResultMatchers.status().isOk())              .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))              .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))              .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("IPhone X"))              .andDo(MockMvcResultHandlers.print())              .andReturn();  }

在這個方法中比較特殊的就是設置 MediaType 類型,因為都是使用 Json 格式,所以設置了 MediaType.APPLICATION_JSON,jsonPath 用於比對期望的數據是否與返回的結果一致,這裡需要注意的是 "$.id" 這 key 的種形式。

四、小結

這裡只是用到了 MockMvc 很小一部分知識,更加深入學習會使你養成一種良好編寫單元測試的習慣,這是十分難得的一種好習慣,推薦去看官方文檔,然後動手去測試一下,為你編寫的每一個 Controller 方法進行測試,保證他們的可靠性。