SpringBoot建立的Maven項目中,會默認添加spring-boot-starter-test依賴。在《5分鐘快速上手SpringBoot》中編寫的單元測試使用了MockMvc。本篇文章就圍繞MockMvc在SpringBoot中的使用進行講解。html
什麼是Mockweb
在面向對象的程序設計中,模擬對象(英語:mock object)是以可控的方式模擬真實對象行爲的假對象。在編程過程當中,一般經過模擬一些輸入數據,來驗證程序是否達到預期結果。spring
爲何使用Mock對象數據庫
使用模擬對象,能夠模擬複雜的、真實的對象行爲。若是在單元測試中沒法使用真實對象,可採用模擬對象進行替代。編程
在如下狀況能夠採用模擬對象來替代真實對象:json
- 真實對象的行爲是不肯定的(例如,當前的時間或溫度);服務器
- 真實對象很難搭建起來;網絡
- 真實對象的行爲很難觸發(例如,網絡錯誤);app
- 真實對象速度很慢(例如,一個完整的數據庫,在測試以前可能須要初始化);異步
- 真實的對象是用戶界面,或包括用戶界面在內;
- 真實的對象使用了回調機制;
- 真實對象可能還不存在;
- 真實對象可能包含不能用做測試(而不是爲實際工做)的信息和方法。
使用Mockito通常分三個步驟:一、模擬測試類所需的外部依賴;二、執行測試代碼;三、判斷執行結果是否達到預期;
MockMvc
MockMvc是由spring-test包提供,實現了對Http請求的模擬,可以直接使用網絡的形式,轉換到Controller的調用,使得測試速度快、不依賴網絡環境。同時提供了一套驗證的工具,結果的驗證十分方便。
接口MockMvcBuilder,提供一個惟一的build方法,用來構造MockMvc。主要有兩個實現:StandaloneMockMvcBuilder和DefaultMockMvcBuilder,分別對應兩種測試方式,即獨立安裝和集成Web環境測試(並不會集成真正的web環境,而是經過相應的Mock API進行模擬測試,無須啓動服務器)。MockMvcBuilders提供了對應的建立方法standaloneSetup方法和webAppContextSetup方法,在使用時直接調用便可。
SpringBoot中使用
第一步:jar包引入。建立SpringBoot項目中默認引入的spring-boot-starter-test間接引入了spring-test,所以無需再額外引入jar包。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope></dependency>
第二步:建立HelloWorldController類,並提供hello方法做爲待測試的業務接口。
public class HelloWorldController {
public String hello(String name){ return "Hello " + name + "!"; }}
第三步:編寫測試類。實例化MockMvc有兩種形式,一種是使用StandaloneMockMvcBuilder,另一種是使用DefaultMockMvcBuilder。測試類及初始化MockMvc初始化:
//SpringBoot1.4版本以前用的是SpringJUnit4Cla***unner.class (SpringRunner.class)//SpringBoot1.4版本以前用的是@SpringApplicationConfiguration(classes = Application.class)@SpringBootTest//測試環境使用,用來表示測試環境使用的ApplicationContext將是WebApplicationContext類型的@WebAppConfigurationpublic class HelloWorldTest {
private MockMvc mockMvc;
private WebApplicationContext webApplicationContext;
public void setup() { // 實例化方式一 mockMvc = MockMvcBuilders.standaloneSetup(new HelloWorldController()).build(); // 實例化方式二// mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); }
單元測試方法:
@Testpublic void testHello() throws Exception {
/* * 一、mockMvc.perform執行一個請求。 * 二、MockMvcRequestBuilders.get("XXX")構造一個請求。 * 三、ResultActions.param添加請求傳值 * 四、ResultActions.accept(MediaType.TEXT_HTML_VALUE))設置返回類型 * 五、ResultActions.andExpect添加執行完成後的斷言。 * 六、ResultActions.andDo添加一個結果處理器,表示要對結果作點什麼事情 * 好比此處使用MockMvcResultHandlers.print()輸出整個響應結果信息。 * 七、ResultActions.andReturn表示執行完成後返回相應的結果。 */ mockMvc.perform(MockMvcRequestBuilders .get("/hello") // 設置返回值類型爲utf-8,不然默認爲ISO-8859-1 .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) .param("name", "Tom")) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string("Hello Tom!")) .andDo(MockMvcResultHandlers.print());}
測試結果打印:
FlashMap: Attributes = null
MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"10"] Content type = application/json;charset=UTF-8 Body = Hello Tom! Forwarded URL = null Redirected URL = null Cookies = []21:34:27.954 INFO 6937 --- [ Thread-2] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
整個過程以下:
一、準備測試環境
二、經過MockMvc執行請求
三、添加驗證斷言
四、添加結果處理器
五、獲得MvcResult進行自定義斷言/進行下一步的異步請求
六、卸載測試環境
注意事項:若是使用DefaultMockMvcBuilder進行MockMvc實例化時需在SpringBoot啓動類上添加組件掃描的package的指定,不然會出現404。如:
@ComponentScan(basePackages = "com.secbro2")
相關API
RequestBuilder提供了一個方法buildRequest(ServletContext servletContext)用於構建MockHttpServletRequest;其主要有兩個子類MockHttpServletRequestBuilder和MockMultipartHttpServletRequestBuilder(如文件上傳使用)。
MockMvcRequestBuilders提供get、post等多種方法用來實例化RequestBuilder。
ResultActions,MockMvc.perform(RequestBuilder requestBuilder)的返回值,提供三種能力:andExpect,添加斷言判斷結果是否達到預期;andDo,添加結果處理器,好比示例中的打印;andReturn,返回驗證成功後的MvcResult,用於自定義驗證/下一步的異步處理。
一些經常使用的測試
1.測試普通控制器
mockMvc.perform(get("/user/{id}", 1)) //執行請求 .andExpect(model().attributeExists("user")) //驗證存儲模型數據 .andExpect(view().name("user/view")) //驗證viewName .andExpect(forwardedUrl("/WEB-INF/jsp/user/view.jsp"))//驗證視圖渲染時forward到的jsp .andExpect(status().isOk())//驗證狀態碼 .andDo(print()); //輸出MvcResult到控制檯
2.獲得MvcResult自定義驗證
MvcResult result = mockMvc.perform(get("/user/{id}", 1))//執行請求 .andReturn(); //返回MvcResult Assert.assertNotNull(result.getModelAndView().getModel().get("user")); //自定義斷言
3.驗證請求參數綁定到模型數據及Flash屬性
mockMvc.perform(post("/user").param("name", "zhang")) //執行傳遞參數的POST請求(也能夠post("/user?name=zhang")) .andExpect(handler().handlerType(UserController.class)) //驗證執行的控制器類型 .andExpect(handler().methodName("create")) //驗證執行的控制器方法名 .andExpect(model().hasNoErrors()) //驗證頁面沒有錯誤 .andExpect(flash().attributeExists("success")) //驗證存在flash屬性 .andExpect(view().name("redirect:/user")); //驗證視圖
4.文件上傳
byte[] bytes = new byte[] {1, 2}; mockMvc.perform(fileUpload("/user/{id}/icon", 1L).file("icon", bytes)) //執行文件上傳 .andExpect(model().attribute("icon", bytes)) //驗證屬性相等性 .andExpect(view().name("success")); //驗證視圖
5.JSON請求/響應驗證
String requestBody = "{\"id\":1, \"name\":\"zhang\"}"; mockMvc.perform(post("/user") .contentType(MediaType.APPLICATION_JSON).content(requestBody) .accept(MediaType.APPLICATION_JSON)) //執行請求 .andExpect(content().contentType(MediaType.APPLICATION_JSON)) //驗證響應contentType .andExpect(jsonPath("$.id").value(1)); //使用Json path驗證JSON 請參考http://goessner.net/articles/JsonPath/ String errorBody = "{id:1, name:zhang}"; MvcResult result = mockMvc.perform(post("/user") .contentType(MediaType.APPLICATION_JSON).content(errorBody) .accept(MediaType.APPLICATION_JSON)) //執行請求 .andExpect(status().isBadRequest()) //400錯誤請求 .andReturn(); Assert.assertTrue(HttpMessageNotReadableException.class.isAssignableFrom(result.getResolvedException().getClass()));//錯誤的請求內容體
6.異步測試
//Callable MvcResult result = mockMvc.perform(get("/user/async1?id=1&name=zhang")) //執行請求 .andExpect(request().asyncStarted()) .andExpect(request().asyncResult(CoreMatchers.instanceOf(User.class))) //默認會等10秒超時 .andReturn(); mockMvc.perform(asyncDispatch(result)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id").value(1));
7.全局配置
mockMvc = webAppContextSetup(wac) .defaultRequest(get("/user/1").requestAttr("default", true)) //默認請求 若是其是Mergeable類型的,會自動合併的哦mockMvc.perform中的RequestBuilder .alwaysDo(print()) //默認每次執行請求後都作的動做 .alwaysExpect(request().attribute("default", true)) //默認每次執行後進行驗證的斷言 .build(); mockMvc.perform(get("/user/1")) .andExpect(model().attributeExists("user"));部份內容參考:https://www.cnblogs.com/lyy-2016/p/6122144.html