<!--mockito依賴--><dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.7.19</version> <scope>test</scope></dependency><!-- junit依賴 --><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope></dependency>
1)Mockito:簡單輕量級的做mocking測試的框架;2)mock對象:在調試期間用來作為真實對象的替代品;3)mock測試:在測試過程中,對那些不容易構建的對象用一個虛擬對象來代替測試的方法就叫mock測試;4)stub:打樁,就是為mock對象的方法指定返回值(可拋出異常);5)verify:行為驗證,驗證指定方法調用情況(是否被調用,調用次數等);
@Testpublic void test0() { //1、創建mock對象(模擬依賴的對象) final List mock = Mockito.mock(List.class); //2、使用mock對象(mock對象會對接口或類的方法給出默認實現) System.out.println("mock.add result => " + mock.add("first")); //false System.out.println("mock.size result => " + mock.size()); //0 //3、打樁操作(狀態測試:設置該對象指定方法被調用時的返回值) Mockito.when(mock.get(0)).thenReturn("second"); Mockito.doReturn(66).when(mock).size(); //3、使用mock對象的stub(測試打樁結果) System.out.println("mock.get result => " + mock.get(0)); //second System.out.println("mock.size result => " + mock.size()); //66 //4、驗證交互 verification(行為測試:驗證方法調用情況) Mockito.verify(mock).get(Mockito.anyInt()); Mockito.verify(mock, Mockito.times(2)).size(); //5、驗證返回的結果(這是JUnit的功能) assertEquals("second", mock.get(0)); assertEquals(66, mock.size());}
?一旦mock對象被創建了,mock對象會記住所有的交互,然后你就可以選擇性的驗證你感興趣的交互,驗證不通過則拋出異常。
@Testpublic void test1() { final List mockList = Mockito.mock(List.class); mockList.add("mock1"); mockList.get(0); mockList.size(); mockList.clear(); // 驗證方法被使用(默認1次) Mockito.verify(mockList).add("mock1"); // 驗證方法被使用1次 Mockito.verify(mockList, Mockito.times(1)).get(0); // 驗證方法至少被使用1次 Mockito.verify(mockList, Mockito.atLeast(1)).size(); // 驗證方法沒有被使用 Mockito.verify(mockList, Mockito.never()).contains("mock2"); // 驗證方法至多被使用5次 Mockito.verify(mockList, Mockito.atMost(5)).clear(); // 指定方法調用超時時間 Mockito.verify(mockList, timeout(100)).get(0); // 指定時間內需要完成的次數 Mockito.verify(mockList, timeout(200).atLeastOnce()).size();}
?默認情況下,所有的函數都有返回值。mock函數默認返回的是null,一個空的集合或者一個被對象類型包裝的內置類型,例如0、false對應的對象類型為Integer、Boolean;
?一旦測試樁函數被調用,該函數將會一致返回固定的值;
?對于 static 和 final 方法, Mockito 無法對其 when(…).thenReturn(…) 操作。
@Testpublic void test2() { //靜態導入,減少代碼量:import static org.mockito.Mockito.*; final ArrayList mockList = mock(ArrayList.class); // 設置方法調用返回值 when(mockList.add("test2")).thenReturn(true); doReturn(true).when(mockList).add("test2"); System.out.println(mockList.add("test2")); //true // 設置方法調用拋出異常 when(mockList.get(0)).thenThrow(new RuntimeException()); doThrow(new RuntimeException()).when(mockList).get(0); System.out.println(mockList.get(0)); //throw RuntimeException // 無返回方法打樁 doNothing().when(mockList).clear(); // 為回調做測試樁(對方法返回進行攔截處理) final Answer<String> answer = new Answer<String>() { @Override public String answer(InvocationOnMock invocationOnMock) throws Throwable { final List mock = (List) invocationOnMock.getMock(); return "mock.size result => " + mock.size(); } }; when(mockList.get(1)).thenAnswer(answer); doAnswer(answer).when(mockList).get(1); System.out.println(mockList.get(1)); //mock.size result => 0 // 對同一方法多次打樁,以最后一次為準 when(mockList.get(2)).thenReturn("test2_1"); when(mockList.get(2)).thenReturn("test2_2"); System.out.println(mockList.get(2)); //test2_2 System.out.println(mockList.get(2)); //test2_2 // 設置多次調用同類型結果 when(mockList.get(3)).thenReturn("test2_1", "test2_2"); when(mockList.get(3)).thenReturn("test2_1").thenReturn("test2_2"); System.out.println(mockList.get(3)); //test2_1 System.out.println(mockList.get(3)); //test2_2 // 為連續調用做測試樁(為同一個函數調用的不同的返回值或異常做測試樁) when(mockList.get(4)).thenReturn("test2").thenThrow(new RuntimeException()); doReturn("test2").doThrow(new RuntimeException()).when(mockList).get(4); System.out.println(mockList.get(4)); //test2 System.out.println(mockList.get(4)); //throw RuntimeException // 無打樁方法,返回默認值 System.out.println(mockList.get(99)); //null}
?參數匹配器使驗證和測試樁變得更靈活;
?為了合理的使用復雜的參數匹配,使用equals()與anyX() 的匹配器會使得測試代碼更簡潔、簡單。有時,會迫使你重構代碼以使用equals()匹配或者實現equals()函數來幫助你進行測試;
?如果你使用參數匹配器,所有參數都必須由匹配器提供;
?支持自定義參數匹配器;
@Testpublic void test3() { final Map mockMap = mock(Map.class); // 正常打樁測試 when(mockMap.get("key")).thenReturn("value1"); System.out.println(mockMap.get("key")); //value1 // 為靈活起見,可使用參數匹配器 when(mockMap.get(anyString())).thenReturn("value2"); System.out.println(mockMap.get(anyString())); //value2 System.out.println(mockMap.get("test_key")); //value2 System.out.println(mockMap.get(0)); //null // 多個入參時,要么都使用參數匹配器,要么都不使用,否則會異常 when(mockMap.put(anyString(), anyInt())).thenReturn("value3"); System.out.println(mockMap.put("key3", 3)); //value3 System.out.println(mockMap.put(anyString(), anyInt())); //value3 System.out.println(mockMap.put("key3", anyInt())); //異常 // 行為驗證時,也支持使用參數匹配器 verify(mockMap, atLeastOnce()).get(anyString()); verify(mockMap).put(anyString(), eq(3)); // 自定義參數匹配器 final ArgumentMatcher<ArgumentTestRequest> myArgumentMatcher = new ArgumentMatcher<ArgumentTestRequest>() { @Override public boolean matches(ArgumentTestRequest request) { return "name".equals(request.getName()) || "value".equals(request.getValue()); } }; // 自定義參數匹配器使用 final ArgumentTestService mock = mock(ArgumentTestService.class); when(mock.argumentTestMethod(argThat(myArgumentMatcher))).thenReturn("success"); doReturn("success").when(mock).argumentTestMethod(argThat(myArgumentMatcher)); System.out.println(mock.argumentTestMethod(new ArgumentTestRequest("name", "value"))); // success System.out.println(mock.argumentTestMethod(new ArgumentTestRequest())); //null}
?驗證執行順序是非常靈活的-你不需要一個一個的驗證所有交互,只需要驗證你感興趣的對象即可;
?你可以僅通過那些需要驗證順序的mock對象來創建InOrder對象;
@Testpublic void test4() { // 驗證同一個對象多個方法的執行順序 final List mockList = mock(List.class); mockList.add("first"); mockList.add("second"); final InOrder inOrder = inOrder(mockList); inOrder.verify(mockList).add("first"); inOrder.verify(mockList).add("second"); // 驗證多個對象多個方法的執行順序 final List mockList1 = mock(List.class); final List mockList2 = mock(List.class); mockList1.get(0); mockList1.get(1); mockList2.get(0); mockList1.get(2); mockList2.get(1); final InOrder inOrder1 = inOrder(mockList1, mockList2); inOrder1.verify(mockList1).get(0); inOrder1.verify(mockList1).get(2); inOrder1.verify(mockList2).get(1);}
?一些用戶可能會在頻繁地使用verifyNoMoreInteractions(),甚至在每個測試函數中都用。但是verifyNoMoreInteractions()并不建議在每個測試函數中都使用;
?verifyNoMoreInteractions()在交互測試套件中只是一個便利的驗證,它的作用是當你需要驗證是否存在冗余調用時;
@Testpublic void test5() { // 驗證某個交互是否從未被執行 final List mock = mock(List.class); mock.add("first"); verify(mock, never()).add("test5"); //通過 verify(mock, never()).add("first"); //異常 // 驗證mock對象沒有交互過 final List mock1 = mock(List.class); final List mock2 = mock(List.class); verifyZeroInteractions(mock1); //通過 verifyNoMoreInteractions(mock1, mock2); //通過 verifyZeroInteractions(mock, mock2); //異常 // 注意:可能只想驗證前面的邏輯,但是加上最后一行,會導致出現異常。建議使用方法層面的驗證,如:never(); // 在驗證是否有冗余調用的時候,可使用此種方式。如下: final List mockList = mock(List.class); mockList.add("one"); mockList.add("two"); verify(mockList).add("one"); // 通過 verify(mockList, never()).get(0); //通過 verifyZeroInteractions(mockList); //異常}
注意!下面這句代碼需要在運行測試函數之前被調用,一般放到測試類的基類或者test runner中:
MockitoAnnotations.initMocks(this);
也可以使用內置的runner: MockitoJUnitRunner 或者一個rule : MockitoRule;
// 代替 mock(ArgumentTestService.class) 創建mock對象;@Mockprivate ArgumentTestService argumentTestService;// 若改注解修飾的對象有成員變量,@Mock定義的mock對象會被自動注入;@InjectMocksprivate MockitoAnnotationServiceImpl mockitoAnnotationService;@Testpublic void test6() { // 注意!下面這句代碼需要在運行測試函數之前被調用,一般放到測試類的基類或者test runner中; MockitoAnnotations.initMocks(this); when(argumentTestService.argumentTestMethod(new ArgumentTestRequest())).thenReturn("success"); System.out.println(argumentTestService.argumentTestMethod(new ArgumentTestRequest())); //success System.out.println(mockitoAnnotationService.mockitoAnnotationTestMethod()); //null}
?可以為真實對象創建一個監控(spy)對象。當你使用這個spy對象時真實的對象也會也調用,除非它的函數被stub了;
?盡量少使用spy對象,使用時也需要小心形式,例如spy對象可以用來處理遺留代碼;
?stub語法中同樣提供了部分mock的方法,可以調用真實的方法;
完全mock:
上文講的內容是完全mock,即創建的mock對象與真實對象無關,mock對象的方法默認都是基本的實現,返回基本類型。可基于接口、實現類創建mock對象。
部分mock:
所謂部分mock,即創建的mock對象時基于真實對象的,mock對象的方法都是默認使用真實對象的方法,除非stub之后,才會以stub為準?;趯崿F類創建mock對象,否則在沒有stub的情況下,調用真實方法時,會出現異常。
注意點:
Mockito并不會為真實對象代理函數調用,實際上它會拷貝真實對象。因此如果你保留了真實對象并且與之交互,不要期望從監控對象得到正確的結果。 當你在監控對象上調用一個沒有被stub的函數時并不會調用真實對象的對應函數,你不會在真實對象上看到任何效果
@Testpublic void test7() { // stub部分mock(stub中使用真實調用)。注意:需要mock實現類,否則會有異常 final StubTestService stubTestService = mock(StubTestServiceImpl.class); when(stubTestService.stubTestMethodA("paramA")).thenCallRealMethod(); doCallRealMethod().when(stubTestService).stubTestMethodB(); System.out.println(stubTestService.stubTestMethodA("paramA")); //stubTestMethodA is called, param = paramA System.out.println(stubTestService.stubTestMethodB()); //stubTestMethodB is called System.out.println(stubTestService.stubTestMethodC()); //null // spy部分mock final LinkedList<String> linkedList = new LinkedList(); final LinkedList spy = spy(linkedList); spy.add("one"); spy.add("two"); doReturn(100).when(spy).size(); when(spy.get(0)).thenReturn("one_test"); System.out.println(spy.size()); //100 System.out.println(spy.get(0)); //one_test System.out.println(spy.get(1)); //two // spy可以類比AOP。在spy中,由于默認是調用真實方法,所以第二種寫法不等價于第一種寫法,不推薦這種寫法。 doReturn("two_test").when(spy).get(2); when(spy.get(2)).thenReturn("two_test"); //異常 java.lang.IndexOutOfBoundsException: Index: 2, Size: 2 System.out.println(spy.get(2)); //two_test // spy對象只是真實對象的復制,真實對象的改變不會影響spy對象 final List<String> arrayList = new ArrayList<>(); final List<String> spy1 = spy(arrayList); spy1.add(0, "one"); System.out.println(spy1.get(0)); //one arrayList.add(0, "list1"); System.out.println(arrayList.get(0)); //list1 System.out.println(spy1.get(0)); //one // 若對某個方法stub之后,又想調用真實的方法,可以使用reset(spy) final ArrayList<String> arrayList1 = new ArrayList<>(); final ArrayList<String> spy2 = spy(arrayList1); doReturn(100).when(spy2).size(); System.out.println(spy2.size()); //100 reset(spy2); System.out.println(spy2.size()); //0}
?@Mock 等價于 Mockito.mock(Object.class);
?@Spy 等價于 Mockito.spy(obj);
區分是mock對象還是spy對象:Mockito.mockingDetails(someObject).isMock();Mockito.mockingDetails(someObject).isSpy();@Mockprivate StubTestService stubTestService;@Spyprivate StubTestServiceImpl stubTestServiceImpl;@Spyprivate StubTestService stubTestServiceImpl1 = new StubTestServiceImpl();@Testpublic void test8() { MockitoAnnotations.initMocks(this); // mock對象返回默認 System.out.println(stubTestService.stubTestMethodB()); //null // spy對象調用真實方法 System.out.println(stubTestServiceImpl.stubTestMethodC()); //stubTestMethodC is called System.out.println(stubTestServiceImpl1.stubTestMethodA("spy")); //stubTestMethodA is called, param = spy // 區分是mock對象還是spy對象 System.out.println(mockingDetails(stubTestService).isMock()); //true System.out.println(mockingDetails(stubTestService).isSpy()); //false System.out.println(mockingDetails(stubTestServiceImpl).isSpy()); //true}
?在某些場景中,不光要對方法的返回值和調用進行驗證,同時需要驗證一系列交互后所傳入方法的參數。那么我們可以用參數捕獲器來捕獲傳入方法的參數進行驗證,看它是否符合我們的要求。
ArgumentCaptor介紹
通過ArgumentCaptor對象的forClass(Class
ArgumentCaptor的Api
argument.capture() 捕獲方法參數
argument.getValue() 獲取方法參數值,如果方法進行了多次調用,它將返回最后一個參數值
argument.getAllValues() 方法進行多次調用后,返回多個參數值
@Testpublic void test9() { List mock = mock(List.class); List mock1 = mock(List.class); mock.add("John"); mock1.add("Brian"); mock1.add("Jim"); // 獲取方法參數 ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); verify(mock).add(argument.capture()); System.out.println(argument.getValue()); //John // 多次調用獲取最后一次 ArgumentCaptor argument1 = ArgumentCaptor.forClass(String.class); verify(mock1, times(2)).add(argument1.capture()); System.out.println(argument1.getValue()); //Jim // 獲取所有調用參數 System.out.println(argument1.getAllValues()); //[Brian, Jim]}
@Mockprivate List<String> captorList;@Captorprivate ArgumentCaptor<String> argumentCaptor;@Testpublic void test10() { MockitoAnnotations.initMocks(this); captorList.add("cap1"); captorList.add("cap2"); System.out.println(captorList.size()); verify(captorList, atLeastOnce()).add(argumentCaptor.capture()); System.out.println(argumentCaptor.getAllValues());}
@Testpublic void test11() { final ArrayList arrayList = mock(ArrayList.class); arrayList.add("one"); arrayList.add("two"); verify(arrayList, description("size()沒有調用")).size(); // org.mockito.exceptions.base.MockitoAssertionError: size()沒有調用 verify(arrayList, timeout(200).times(3).description("驗證失敗")).add(anyString()); //org.mockito.exceptions.base.MockitoAssertionError: 驗證失敗}
?可以指定策略來創建mock對象的返回值。這是一個高級特性,通常來說,你不需要寫這樣的測試;
?它對于遺留系統來說是很有用處的。當你不需要為函數調用打樁時你可以指定一個默認的answer;
@Testpublic void test12(){ // 創建mock對象、使用默認返回 final ArrayList mockList = mock(ArrayList.class); System.out.println(mockList.get(0)); //null // 這個實現首先嘗試全局配置,如果沒有全局配置就會使用默認的回答,它返回0,空集合,null,等等。 // 參考返回配置:ReturnsEmptyValues mock(ArrayList.class, Answers.RETURNS_DEFAULTS); // ReturnsSmartNulls首先嘗試返回普通值(0,空集合,空字符串,等等)然后它試圖返回SmartNull。 // 如果最終返回對象,那么會簡單返回null。一般用在處理遺留代碼。 // 參考返回配置:ReturnsMoreEmptyValues mock(ArrayList.class, Answers.RETURNS_SMART_NULLS); // 未stub的方法,會調用真實方法。 // 注1:存根部分模擬使用時(mock.getSomething ()) .thenReturn (fakeValue)語法將調用的方法。對于部分模擬推薦使用doReturn語法。 // 注2:如果模擬是序列化反序列化,那么這個Answer將無法理解泛型的元數據。 mock(ArrayList.class, Answers.CALLS_REAL_METHODS); // 深度stub,用于嵌套對象的mock。參考:https://www.cnblogs.com/Ming8006/p/6297333.html mock(ArrayList.class, Answers.RETURNS_DEEP_STUBS); // ReturnsMocks首先嘗試返回普通值(0,空集合,空字符串,等等)然后它試圖返回mock。 // 如果返回類型不能mocked(例如是final)然后返回null。 mock(ArrayList.class, Answers.RETURNS_MOCKS); // mock對象的方法調用后,可以返回自己(類似builder模式) mock(ArrayList.class, Answers.RETURNS_SELF); // 自定義返回 final Answer<String> answer = new Answer<String>() { @Override public String answer(InvocationOnMock invocation) throws Throwable { return "test_answer"; } }; final ArrayList mockList1 = mock(ArrayList.class, answer); System.out.println(mockList1.get(0)); //test_answer}
測試實體類
@Datapublic class User { /** * 姓名,登錄密碼 */
持久層DAO
public interface UserDao { /** * 根據name查找user * @param name * @return */ User getUserByName(String name); /** * 保存user * @param user * @return */ Integer saveUser(User user);}
業務層Service接口
public interface UserService { /** * 根據name查找user * @param name * @return */ User getUserByName(String name); /** * 保存user * @param user * @return */ Integer saveUser(User user);}
業務層Serive實現類
@Servicepublic class UserServiceImpl implements UserService { //userDao @Autowired private UserDao userDao; /** * 根據name查找user * @param name * @return */ @Override public User getUserByName(String name) { try { return userDao.getUserByName(name); } catch (Exception e) { throw new RuntimeException("查詢user異常"); } } /** * 保存user * @param user * @return */ @Override public Integer saveUser(User user) { if (userDao.getUserByName(user.getName()) != null) { throw new RuntimeException("用戶名已存在"); } try { return userDao.saveUser(user); } catch (Exception e) { throw new RuntimeException("保存用戶異常"); } }}
現在我們的Service寫好了,想要單元測試一下,但是Dao是其他人開發的,目前還沒有寫好,那我們如何測試呢?
public class UserServiceTest { /** * Mock測試:根據name查詢user */ @Test public void getUserByNameTest() { // mock對象 final UserDao userDao = mock(UserDao.class); final UserServiceImpl userService = new UserServiceImpl(); userService.setUserDao(userDao); // stub調用 final User user = new User(); user.setName("admin"); user.setPassword("pass"); when(userDao.getUserByName("admin")).thenReturn(user); // 執行待測試方法 final User user1 = userService.getUserByName("admin"); System.out.println("查詢結果:" + JacksonUtil.obj2json(user1)); //查詢結果:{"name":"admin","password":"pass"} // 驗證mock對象交互 verify(userDao).getUserByName(anyString()); // 驗證查詢結果 Assert.assertNotNull("查詢結果為空!", user1); Assert.assertEquals("查詢結果錯誤!", "admin", user1.getName()); } /** * Mock測試:保存user */ @Mock private UserDao userDao; @InjectMocks private UserServiceImpl userService; @Test public void saveUserTest() throws Exception{ // 執行注解初始化 MockitoAnnotations.initMocks(this); // mock對象stub操作 final User user = new User(); user.setName("admin"); user.setPassword("pass"); when(userDao.getUserByName("admin")).thenReturn(user).thenReturn(null); when(userDao.saveUser(any(User.class))).thenReturn(1); // 驗證用戶名重復的情況 try { userService.saveUser(user); throw new Exception(); //走到這里說明驗證失敗 } catch (RuntimeException e) { System.out.println("重復用戶名保存失敗-測試通過"); //重復用戶名保存失敗-測試通過 } verify(userDao).getUserByName("admin"); // 驗證正常保存的情況 user.setName("user"); final Integer integer = userService.saveUser(user); System.out.println("保存結果:" + integer); //保存結果:1 Assert.assertEquals("保存失?。?, 1, integer.longValue()); verify(userDao).saveUser(any(User.class)); verify(userDao, times(2)).getUserByName(anyString()); }}
根據以上代碼我們可以知道,當我們的待測類開發完成而依賴的類的實現還沒有開發完成。此時,我們就可以用到我們的Mock測試,模擬我們依賴類的返回值,使我們的待測類與依賴類解耦。這樣,我們就可以對我們的待測類進行單元測了。
本文鏈接:http://www.tebozhan.com/showinfo-26-12330-0.html一文淺談Mockito使用
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 測試驅動開發實踐:如何使用 Xunit 框架進行單元測試和集成測試
下一篇: JVM 架構—JVM 內部是如何工作的?