SpringBoot項目,SpringBoot基礎之MockMvc單元測試

 2023-11-18 阅读 27 评论 0

摘要:SpringBoot創建的Maven項目中,會默認添加spring-boot-starter-test依賴。在《5分鐘快速上手SpringBoot》中編寫的單元測試使用了MockMvc。本篇文章就圍繞MockMvc在SpringBoot中的使用進行講解。 什么是Mock 在面向對象的程序設計中,模擬對象(英語:

SpringBoot創建的Maven項目中,會默認添加spring-boot-starter-test依賴。在《5分鐘快速上手SpringBoot》中編寫的單元測試使用了MockMvc。本篇文章就圍繞MockMvc在SpringBoot中的使用進行講解。

什么是Mock

在面向對象的程序設計中,模擬對象(英語:mock object)是以可控的方式模擬真實對象行為的假對象。在編程過程中,通常通過模擬一些輸入數據,來驗證程序是否達到預期結果。

為什么使用Mock對象

使用模擬對象,可以模擬復雜的、真實的對象行為。如果在單元測試中無法使用真實對象,可采用模擬對象進行替代。

在以下情況可以采用模擬對象來替代真實對象:

  • 真實對象的行為是不確定的(例如,當前的時間或溫度);
  • 真實對象很難搭建起來;
  • 真實對象的行為很難觸發(例如,網絡錯誤);
  • 真實對象速度很慢(例如,一個完整的數據庫,在測試之前可能需要初始化);
  • 真實的對象是用戶界面,或包括用戶界面在內;
  • 真實的對象使用了回調機制;
  • 真實對象可能還不存在;
  • 真實對象可能包含不能用作測試(而不是為實際工作)的信息和方法。

使用Mockito一般分三個步驟:1、模擬測試類所需的外部依賴;2、執行測試代碼;3、判斷執行結果是否達到預期;

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方法作為待測試的業務接口。

@RestController
public class HelloWorldController {@RequestMappingpublic String hello(String name){return "Hello " + name + "!";}
}

第三步:編寫測試類。實例化MockMvc有兩種形式,一種是使用StandaloneMockMvcBuilder,另外一種是使用DefaultMockMvcBuilder。測試類及初始化MockMvc初始化:

//SpringBoot1.4版本之前用的是SpringJUnit4ClassRunner.class
@RunWith(SpringRunner.class)
//SpringBoot1.4版本之前用的是@SpringApplicationConfiguration(classes = Application.class)
@SpringBootTest
//測試環境使用,用來表示測試環境使用的ApplicationContext將是WebApplicationContext類型的
@WebAppConfiguration
public class HelloWorldTest {private MockMvc mockMvc;@Autowiredprivate WebApplicationContext webApplicationContext;@Beforepublic void setup() {// 實例化方式一mockMvc = MockMvcBuilders.standaloneSetup(new HelloWorldController()).build();// 實例化方式二
//		mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();}

單元測試方法:

@Test
public void testHello() throws Exception {/** 1、mockMvc.perform執行一個請求。* 2、MockMvcRequestBuilders.get("XXX")構造一個請求。* 3、ResultActions.param添加請求傳值* 4、ResultActions.accept(MediaType.TEXT_HTML_VALUE))設置返回類型* 5、ResultActions.andExpect添加執行完成后的斷言。* 6、ResultActions.andDo添加一個結果處理器,表示要對結果做點什么事情*   比如此處使用MockMvcResultHandlers.print()輸出整個響應結果信息。* 7、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 = nullMockHttpServletResponse:Status = 200Error message = nullHeaders = [Content-Type:"application/json;charset=UTF-8", Content-Length:"10"]Content type = application/json;charset=UTF-8Body = Hello Tom!Forwarded URL = nullRedirected URL = nullCookies = []
2019-04-02 21:34:27.954  INFO 6937 --- [       Thread-2] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

整個過程如下:
1、準備測試環境
2、通過MockMvc執行請求
3、添加驗證斷言
4、添加結果處理器
5、得到MvcResult進行自定義斷言/進行下一步的異步請求
6、卸載測試環境

注意事項:如果使用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"));

關注微信公眾:程序新視界,回復“mockmvc”即可獲得本篇內容PDF版本,更多免費資料,期待你的到來。

Spring技術視頻

CSDN學院:《Spring Boot 視頻教程全家桶》


程序新視界

關注程序員的職場生涯,大量優質學習資源

csdn-微信公眾號

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/178488.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息