Mockito是基於CGLIB代理,實現打樁。它通過攔截對象的所有操作方法,對於滿足打樁條件的調用,返回預設的返回值。
主要註解
@InjectMocks
用於標記對象屬性允許用mock或spy注入。嘗試通過按「先構造函數注入再setter注入最後屬性(字段)注入」的順序注入依賴。
-
構造函數注入:選取最大的構造函數,用已聲明的mock作爲參數注入;注:如果已經通過構造注入,將不再嘗試其他策略注入(即不會再有如下兩種的注入);
-
setter注入:先通過類型匹配,如果匹配的類型有多個mock實例,再通過名字匹配;
-
字段注入:同setter注入(不同的是字段可以不提供setter方法),先類型再名字;
@Mock
用於標記一個Mock字段,被標記的對象將被創建爲mock對象(對於沒有打樁的方法,返回值爲默認值0或null或false);
- 允許多次代理mock對象的同一個方法,但具體的行爲取決於該方法最近的一次代理行爲
- mock對象的代理方法,允許多次調用,只要不覆蓋它的代理行爲,那麼每次調用的執行相同的行爲或者返回相同的值
- 相同的方法和參數唯一確認一個代理。比如你可以分別代理get(int)方法在參數分別爲0和1時的不同行爲
@Spy
與@Mock不同的是,mock對象是基於真實的實例創建,對mock對象的方法調用同時也會調用真實對象的方法;
@Test
public void whenSpyingOnList_thenCorrect() {
List<String> list = new ArrayList<String>();
List<String> spyList = Mockito.spy(list);
spyList.add("one");
spyList.add("two");
Mockito.verify(spyList).add("one");
Mockito.verify(spyList).add("two");
assertEquals(2, spyList.size());
}
可以像使用mock對象時的語法一樣,配置或者說重新改寫spy對象的方法,下面的例子使用了doReturn()來重寫了size()方法:
@Test
public void whenStubASpy_thenStubbed() {
List<String> list = new ArrayList<String>();
List<String> spyList = Mockito.spy(list);
assertEquals(0, spyList.size());
Mockito.doReturn(100).when(spyList).size();
assertEquals(100, spyList.size());
}
注:
-
在監控對象上使用when(Object)來進行打樁有時候是不可能或者不切實際的。
在使用spy的時候,推薦用 doReturn|Answer|Throw() 等一系列的方法來打樁。
List list = new LinkedList(); List spy = spy(list); // 不可能: // 因爲當調用spy.get(0)時會調用真實對象的get(0)函數, // 此時會發生IndexOutOfBoundsException,因爲真實list對象是空的。 when(spy.get(0)).thenReturn("foo"); // 你需要使用 doReturn來進行打樁。 doReturn("foo").when(spy).get(0);
-
另外注意, Mockito並不會爲真實對象代理函數調用,實際上它會複製真實對象。
所以,如果你保留了真實對象並與其交互,不要期望從監控對象得到正確的結果。
當你在監控對象上調用一個沒有被打樁的函數時,並不會調用真實對象的對應函數,因此你不會在真實對象上看到任何結果。 -
Mock vs. Spy
當Mockito創造一個mock對象時,它是從類的類型中創建,而不是從類的實例中創建。也就是說mock對象是一個簡單的只帶有骨架的空殼的對象實例,同時我們可以跟蹤它的所有交互方法。不同的是,spy對象是對一個對象實例進行的封裝,它仍然能夠像正常對象實例一樣有這正常的行爲,唯一不同的是這些行爲交互是可以跟蹤和配置的。
@Test public void whenCreateMock_thenCreated() { List mockedList = Mockito.mock(ArrayList.class); mockedList.add("one"); Mockito.verify(mockedList).add("one"); assertEquals(0, mockedList.size()); } @Test public void whenCreateSpy_thenCreate() { List spyList = Mockito.spy(new ArrayList()); spyList.add("one"); Mockito.verify(spyList).add("one"); assertEquals(1, spyList.size()); }
用法指南
-
基本用法
Mockito.when(list.size()).thenReturn(1);
上述代碼表示,當對list對象調用size()方法時,會返回1.這樣我們就可以自定義mock對象的行爲了。
-
迭代風格的返回值設定
// 第一種方式 when(i.next()).thenReturn("Hello").thenReturn("World"); // 第二種方式 when(i.next()).thenReturn("Hello", "World"); // 第三種方式 when(i.next()).thenReturn("Hello"); when(i.next()).thenReturn("World");
-
沒有返回值的打樁
Mockito.doNothing().when(obj).notify(); // 或直接 when(obj).notify();
-
對被測試的方法強行拋出異常
when(i.next()).thenThrow(new RuntimeException()); // void 方法的 doThrow(new RuntimeException()).when(i).remove();
-
模擬傳入的參數 (Mockito.anyInt())
when(mockedList.get(anyInt())).thenReturn("element"); System.out.println(mockedList.get(999)); // 此時打印是 element
anyString() 匹配任何 String 參數,
anyInt() 匹配任何 int 參數,
anySet() 匹配任何 Set,
any() 則意味着參數爲任意值
再進一步,自定義類型也可以,如 any(User.class)
自定義參數匹配器 可以實現 ArgumentMatcher<?> 接口,
然後 when(mock.addAll(argThat(new YourImplClass()))).thenReturn(true) -
獲取返回的結果: 實現 Answer<?>接口
final Map<String, Object> hash = new HashMap<String, Object>(); Answer<String> answer = new Answer<String>() { public String answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); return hash.get(args[0].toString()).toString(); } }; when(request.getAttribute("errMsg")).thenAnswer(answer);
利用 InvocationOnMock 提供的方法可以獲取 mock 方法的調用信息。下面是它提供的方法:
-
getArguments() 調用後會以 Object 數組的方式返回 mock 方法調用的參數。
-
getMethod() 返回 java.lang.reflect.Method 對象
-
getMock() 返回 mock 對象
-
callRealMethod() 真實方法調用,如果 mock 的是接口它將會拋出異常
void 方法可以獲取參數,只是寫法上有區別,
Answer<String> answer = new Answer<Object>() { public String answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); System.out.println(args[1]); hash.put(args[0].toString(), args[1]); return "called with arguments: " + args; } }; doAnswer(answer).when(request).setAttribute(anyString(), anyString());
-
驗證方法
前面提到的 when(……).thenReturn(……) 屬於狀態測試,
某些時候,測試不關心返回結果,而是側重方法有否被正確的參數調用過,這時候就應該使用 驗證方法了。
從概念上講,就是和狀態測試所不同的“行爲測試”了。
一旦使用 mock() 對模擬對象打樁,意味着 Mockito 會記錄着這個模擬對象調用了什麼方法,還有調用了多少次。
最後由用戶決定是否需要進行驗證,即 verify() 方法。mockedList.add("one"); mockedList.add("two"); // 如果times不傳入,則默認是1, 表示驗證 mockedList 是否被調用了 add("one")方法1次。 verify(mockedList,times(1)).add("one"); Map mock = Mockito.mock( Map.class ); when(mock.get("city")).thenReturn("廣州"); // 關注參數有否傳入 verify(mock).get(Matchers.eq("city")); // verify 也可以使用模擬參數,但是若方法中的某一個參數使用了matcher,則所有的參數都必須使用 matcher。 // 例如 下面的語句會拋出異常, verify(mock).someMethod(anyInt(), anyString(), "third argument"); // 關注調用的次數 verify(mock, times(2)).get(Matchers.eq("city")); //還有 never()==times(0)、atLeast(N)、atLeastOnce()===atLeast(1)、atMost(N) //驗證mock對象是否存在沒有驗證過的調用方法 verifyNoMoreInteractions(mock) ; //驗證mock對象是否沒有進行任何交互 verifyZeroInteractions(mock); //超時驗證 單位毫秒 verify(mock, timeout(100)).someMethod(); //在給定的時間內完成執行次數 verify(mock, timeout(100).times(2)).someMethod(); //給定的時間內至少執行兩次 verify(mock, timeout(100).atLeast(2)).someMethod(); //還有 驗證方法調用的順序 inOrder,不再詳述。 //還有 驗證參數交互時所傳入方法的參數,使用 ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); 不再詳述
-
參數捕捉
@Test public void testCapturingArguments() throws Exception { List mockedList = mock(List.class); ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class); mockedList.add("John"); //驗證後再捕捉參數 verify(mockedList).add(argument.capture()); //驗證參數 assertEquals("John", argument.getValue()); }
注:可用 @Captor 創建ArgumentCaptor
-
部分Mock
//用spy()方法創建部分mock List list = spy(new LinkedList()); //用thenCallRealMethod方法 when(mock.someMethod()).thenCallRealMethod();
原理淺析
-
標記爲InjectMocks的對象注入的mock對象來自哪裏?
標記爲InjectMocks的字段注入的依賴mock爲同一testInstance(一個測試類爲一個testInstance)下的其他mock字段。
通過調用方法 MockitoAnnotations.initMocks(Object testInstance) 實現Mock注入:先分類無依賴mock和有依賴mock;然後先創建無依賴的mock實例,再將創建好的無依賴mock對象集作爲參數創建依有依賴mock實例。
進階——PowerMock
Mockito可以實現常規的一些測試需求,如果有很難甚至無法測試的測試問題,可以考慮PowerMock。PowerMock可以模擬靜態方法,刪除靜態初始化程序,允許模擬而不依賴於注入,等等。PowerMock通過在執行測試時在運行時修改字節碼來完成這些技巧。PowerMock還包含一些實用程序,可以更輕鬆地訪問對象的內部狀態。
參考
mockito wiki
Mockito 源碼解析
Mockito Spy 用法
Mockito測試
Mockito使用指南