例如:測試 controller時,依賴 service,這個時候就能夠假設當調用 service 某個方法時返回指定的某些值,從而來下降引用類所帶來的測試複雜度增長的影響。Mockito就用於這種場景。java
Mockito的使用,通常有如下幾種組合:web
經過Mockito指定打樁對象的返回值時,能夠經過如下方式進行:spring
given用於對指定方法進行返回值的定製,它須要與will開頭的方法一塊兒使用,will開頭的方式根據其接收參數的不一樣,又分紅兩類:一是接收直接值的,如直接指定返回結果爲某個常量;二是接收Answer參數的,能夠騎過Answer類的answer方法來根據傳入參數定製返回結果。json
咱們實際針對的通常是某個類的某個方法;這個方法可能會有輸入參數;考慮這種場景:若是要假設打樁的這個方法,在某個輸入時返回值A;在另一個輸入時返回值爲B;這種場景就能夠經過Answer類來實現。api
案例 根據傳入的參數,返回不一樣的數據springboot
@RunWith(SpringRunner.class) @SpringBootTest public class LearnController2Test { @Autowired private WebApplicationContext wac; private MockMvc mvc; private MockHttpSession session; /** * 1. 對於不須要返回的任何值的類的全部方法,能夠直接使用MockBean * 2. @MockBean 會代理已有的bean的方法,不會執行真實 bean 的具體方法。 */ @MockBean private LearnService learnService; @Before public void setupMockMvc() { //初始化MockMvc對象 mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //構建session session = new MockHttpSession(); User user = new User("root", "root"); //攔截器那邊會判斷用戶是否登陸,因此這裏注入一個用戶 session.setAttribute("user", user); } /** * 獲取教程測試用例 * <p> * get 請求 * <p> * controller 依賴 service 的方法,這裏給 service 方法打樁,不執行真實的方法 * * @throws Exception */ @Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); // 當調用 selectByKey 函數時,返回指定的值 given(this.learnService.selectByKey(Mockito.any())).willAnswer(new Answer<Object>() { /** * InvocationOnMock 經過它能夠獲取打樁方法的實際傳入參數清單 * @param invocationOnMock * @return * @throws Throwable */ @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { Long argumentAt = invocationOnMock.getArgumentAt(0, Long.class); System.out.println("調用方法的實際參數: " + argumentAt); if (argumentAt.equals(Long.parseLong("1001"))) { return learnResource; } return null; } }); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用來獲取author字段比對是否爲嘟嘟MD獨立博客,不是就測試不經過 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD獨立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot乾貨系列")) .andDo(MockMvcResultHandlers.print()); } }
經過willReturn能夠直接指定打樁的方法的返回值
案例 在任何場景下,都返回指定的數據session
/** * 獲取教程測試用例 * <p> * get 請求 * <p> * controller 依賴 service 的方法,這裏給 service 方法打樁,不執行真實的方法 * * @throws Exception */ @Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); given(this.learnService.selectByKey(Mockito.any())).willReturn(learnResource); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用來獲取author字段比對是否爲嘟嘟MD獨立博客,不是就測試不經過 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD獨立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot乾貨系列")) .andDo(MockMvcResultHandlers.print()); } 異常信息: java.lang.AssertionError: JSON path "$.author" Expected :嘟嘟MD獨立博客 Actual :zhang <Click to see difference>
thenReturn與willReturn相似mvc
/** * 獲取教程測試用例 * <p> * get 請求 * <p> * controller 依賴 service 的方法,這裏給 service 方法打樁,不執行真實的方法 * * @throws Exception */ @Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); when(this.learnService.selectByKey(Mockito.any())).thenReturn(learnResource); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用來獲取author字段比對是否爲嘟嘟MD獨立博客,不是就測試不經過 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD獨立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot乾貨系列")) .andDo(MockMvcResultHandlers.print()); }
thenAnswer與willAnswer也相似ide
/** * 獲取教程測試用例 * <p> * get 請求 * <p> * controller 依賴 service 的方法,這裏給 service 方法打樁,不執行真實的方法 * * @throws Exception */ @Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); when(this.learnService.selectByKey(Mockito.any())).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { Long argumentAt = invocationOnMock.getArgumentAt(0, Long.class); System.out.println("調用方法的實際參數: " + argumentAt); if (argumentAt.equals(Long.parseLong("1001"))) { return learnResource; } else if (argumentAt.equals(Long.parseLong("1002"))) { learnResource.setAuthor("keke"); return learnResource; } return null; } }); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1002") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用來獲取author字段比對是否爲嘟嘟MD獨立博客,不是就測試不經過 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD獨立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot乾貨系列")) .andDo(MockMvcResultHandlers.print()); } 異常: 參數爲 1001 時 java.lang.AssertionError: JSON path "$.author" Expected :嘟嘟MD獨立博客 Actual :zhang <Click to see difference> 參數爲 1002 時 java.lang.AssertionError: JSON path "$.author" Expected :嘟嘟MD獨立博客 Actual :keke <Click to see difference>
// mock 對象不能是 @MockBean 生成的,@MockBean請況下不能用 @Test public void testAnswer1() { List<String> mock = Mockito.mock(List.class); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { Object[] args = invocationOnMock.getArguments(); System.out.println(args[0]); Integer num = (Integer) args[0]; if (num > 3) { return "大於三"; } else { return "小於三"; } } }).when(mock).get(Mockito.anyInt()); // 當 索引爲 4 時,指望 大於三 Assert.assertThat(mock.get(4), equalTo("大於三")); // 當 索引爲 2 時,指望 小於三 Assert.assertThat(mock.get(4), equalTo("小於三")); } // mock 對象不能是 @MockBean 生成的,@MockBean請況下不能用 @Test public void testAnswer1() { List<String> mock = Mockito.mock(List.class); Mockito.doReturn("大於三").when(mock).get(Mockito.anyInt()); // 當 索引爲 2 時 Assert.assertThat(mock.get(2), equalTo("大於三")); }
@Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); given(this.learnService.selectByKey(Mockito.any())).willReturn(learnResource); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()); // 判斷 learnService.selectByKey 方法 是否調用了 Mockito.verify(learnService).selectByKey(1001L); // 判斷 learnService.selectByKey 方法,指望調用 2 次,能過 times 函數指定 selectByKey 函數指望調用幾回 // 也能夠經過 Mockito.atLeast 最少幾回,Mockito.atMost 是多幾回 等函數判斷 Mockito.verify(learnService, Mockito.times(2)).selectByKey(1001L); } 異常:由於 learnService.selectByKey 方法,調用了1次,而指望調用兩次,因此測試出錯 org.mockito.exceptions.verification.TooLittleActualInvocations: learnServiceImpl bean.selectByKey(1001); Wanted 2 times: -> at com.dudu.outher.LearnController7Test.qryLearn(LearnController7Test.java:86) But was 1 time: -> at com.dudu.controller.LearnController.qryLearn(LearnController.java:88)
given+willThrow函數
@Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); // 調用 learnService.selectByKey 方法時,拋出異常 given(this.learnService.selectByKey(Mockito.any())).willThrow(new Exception("查詢出錯")); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()); } 異常: org.mockito.exceptions.base.MockitoException: Checked exception is invalid for this method! Invalid: java.lang.Exception: 查詢出錯
when+thenThrow
@Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); when(this.learnService.selectByKey(Mockito.any())).thenThrow(new Exception("查詢出錯")); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用來獲取author字段比對是否爲嘟嘟MD獨立博客,不是就測試不經過 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD獨立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot乾貨系列")) .andDo(MockMvcResultHandlers.print()); } 異常: org.mockito.exceptions.base.MockitoException: Checked exception is invalid for this method! Invalid: java.lang.Exception: 查詢出錯
doThrow+when
不能用於 @MockBean 場景下
@Test public void testAnswer1() { List<String> mock = Mockito.mock(List.class); // 調用 mock.size 時,拋出指望的異常信息 Mockito.doThrow(new Exception("查詢出錯")).when(mock).size(); // 調用 mock 對象的方法 mock.size(); } 異常: org.mockito.exceptions.base.MockitoException: Checked exception is invalid for this method! Invalid: java.lang.Exception: 查詢出錯
doThrow:在模擬對象中調用方法時想要拋出異常時使用.
doReturn:在執行方法時要返回返回值時使用.
doAnswer:須要對傳遞給方法的參數執行一些操做
doNothing:是最簡單的列表,基本上它告訴Mockito在調用模擬對象中的方法時什麼也不作.有時用於void返回方法或沒有反作用的方法,或者與您正在進行的單元測試無關
https://blog.csdn.net/icarusliu/article/details/78860351
Mockito沒法對靜態方法進行Mock,若是須要Mock靜態方法,須要使用到PowerMockito
<dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>1.7.1</version> </dependency>
單元測試時,須要使用PowerMockRunner及PrepareForTest兩個註解
@RunWith(PowerMockRunner.class) // 對 StringUtils 靜態方法進行測試 @PrepareForTest({StringUtils.class}) public class TestStatic { @Test public void testStaticMethod() { // 對 StringUtils 打樁 PowerMockito.mockStatic(StringUtils.class); PowerMockito.when(StringUtils.isNoneBlank(Mockito.anyString())).thenReturn(false); boolean bbb = StringUtils.isNoneBlank("bbb"); System.out.println(bbb); } }
SpringBootTest必需要使用SpringRunner才能生效;但RunWith沒有辦法指定多個,能夠經過PowerMockRunnerDelegate來解決這個問題:
@RunWith(PowerMockRunner.class)//使用powermock提供的代理來使用 @PowerMockRunnerDelegate(SpringRunner.class) @PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"})//忽略一些powermock使用的classloader沒法處理的類 @PrepareForTest({StringUtils.class})// @PrepareForTest 能夠 mock 多個靜態方法 @SpringBootTest public class LearnController11Test { @Autowired private WebApplicationContext wac; private MockMvc mvc; private MockHttpSession session; @MockBean private LearnService learnService; @Before public void setupMockMvc() { //初始化MockMvc對象 mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //構建session session = new MockHttpSession(); User user = new User("root", "root"); //攔截器那邊會判斷用戶是否登陸,因此這裏注入一個用戶 session.setAttribute("user", user); } /** * 獲取教程測試用例 * <p> * get 請求 * <p> * controller 依賴 service 的方法,這裏給 service 方法打樁,不執行真實的方法 * * @throws Exception */ @Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("Spring Boot乾貨系列"); learnResource.setAuthor("嘟嘟MD獨立博客"); learnResource.setId(10L); // 對 service層中的方法進行 mock given(this.learnService.selectByKey(Mockito.any())).willReturn(learnResource); // 對 StringUtils 打樁,mock 靜態方法 PowerMockito.mockStatic(StringUtils.class); // 當 執行 StringUtils.isNoneBlank 方法時,返回 false PowerMockito.when(StringUtils.isNoneBlank(Mockito.anyString())).thenReturn(false); // 實際使用中 StringUtils.isNoneBlank("bbb") 返回 true,但這裏返回 false boolean result = StringUtils.isNoneBlank("bbb"); System.out.println("StringUtils.isNoneBlank: " + result); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用來獲取author字段比對是否爲嘟嘟MD獨立博客,不是就測試不經過 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD獨立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot乾貨系列")) .andDo(MockMvcResultHandlers.print()); } }