SpringCloud的單元測試主要是依靠 Mock以及Mockito, 所以我們需要對Mock以及Mockito有一定的認識。
一、為什么要用MockMvc
可能我們在測試控制層的代碼都是啟動服務器,在瀏覽器中輸入URL,然后開始測試是否達到預期效果,發生錯誤的話,修改相關代碼並重啟服務器再次進行測試。分析一下這個過程,啟動服務器-->打開瀏覽器-->輸入URL-->等待返回結果-->修復bug-->重啟服務器.....循環。
其中的缺點也挺明顯的,在瀏覽器輸入URL的地址,如果是GET請求還好,POST請求或者DELETE請求怎么辦?只能借助其他工具,通過命令行編寫curl語句,或者借助谷歌瀏覽器的postman插件,亦或者自己在代碼中通過編寫相應httpClient方法來實現測試,但是這幾種方法都較為麻煩,而且測試用例並不能較好的保存。再說一個缺點,代碼修改后,往往需要再次重啟服務器,等待啟動完畢才能接下來的測試過程。
如果tomcat服務器啟動速度較慢,這將是一件非常痛苦的事情,測試驗證也不方便,且依賴網絡環境,這些原因導致測試起來很麻煩,而為了可以方便對Controller進行測試,且很好的保存和循環使用測試用例,則可以通過單元測試來解決,通過前面一篇文章,大家對於單元測試的便利性有了認識和體會,接下來通過引入MockMVC進行控制層的單元測試。
MockMvc實現了對Http請求的模擬,能夠直接使用網絡的形式,轉換到Controller的調用,這樣可以使得測試速度快、不依賴網絡環境,而且提供了一套驗證的工具,這樣可以使得請求的驗證統一而且很方便。
二、示例演示
1. pom.xml中引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
2. 編寫測試類
在測試類前加上注解
@RunWith(SpringRunner.class) @SpringBootTest(classes = EurekaClientApplication.class) //EurekaClientApplication是模塊的啟動類名 @WebAppConfiguration @ContextConfiguration
再加上
@Autowired private WebApplicationContext context; private MockMvc mvc;
再加上一個before
@Before public void setUp() throws Exception { mvc = MockMvcBuilders .webAppContextSetup(context) .build(); }
在真實需要測試的代碼中加入
@Test public void contextLoads() throws Exception { MvcResult //groupManager訪問路徑 //param傳入參數 result=mvc.perform(MockMvcRequestBuilders.post("/groupManager").param("pageNum","1").param("pageSize","10")).andReturn(); MockHttpServletResponse response = result.getResponse(); String content = response.getContentAsString(); List<JtInfoDto> jtInfoDtoList = GsonUtils.toObjects(content, new TypeToken<List<JtInfoDto>>() {}.getType()); for(JtInfoDto infoDto : jtInfoDtoList){ System.out.println(infoDto.getJtCode()); } }
3. 數據庫回滾
在測試類上添加注解@Rollback,同時測試方法加上注解@Transactional。
如果不希望回滾 將rollback改為false即可
4. 完整的測試代碼
@RunWith(SpringRunner.class) @SpringBootTest(classes = EurekaClientApplication.class) @WebAppConfiguration @ContextConfiguration public class EurekaClientApplicationTests { @Autowired private WebApplicationContext context; private MockMvc mvc; @Before public void setUp() throws Exception { mvc = MockMvcBuilders .webAppContextSetup(context) .build(); } @Test public void contextLoads() throws Exception { MvcResult result=mvc.perform(MockMvcRequestBuilders.post("/groupManager").param("pageNum","1").param("pageSize","10")).andReturn(); MockHttpServletResponse response = result.getResponse(); String content = response.getContentAsString(); List<JtInfoDto> jtInfoDtoList = GsonUtils.toObjects(content, new TypeToken<List<JtInfoDto>>() {}.getType()); for(JtInfoDto infoDto : jtInfoDtoList){ System.out.println(infoDto.getJtCode()); } } }
注意:我們大多數的時候是往控制層controller傳遞的不是一個簡單的參數,可能是一個Map或一個實體類,發現怎么都傳不過去,解決方法如下:
在發送請求的時候,我們使用json的參數格式
Map params = new HashMap(); params.put("name", "西瓜"); params.put("unit","斤"); params.put("price", "12.88"); String requestJson = JSONObject.toJSONString(params); MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/good/save") .contentType(MediaType.APPLICATION_JSON).content(requestJson)) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.status().is(200)) .andReturn(); result.getResponse().setCharacterEncoding("UTF-8"); System.out.println(result.getResponse().getContentAsString());
然后controller的參數添加@RequestBody注解。
