import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mockito; import java.util.List; import java.util.Map; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.*; /** * Created by MyWorld on 2016/1/26. */ public class MockitoDemo { @Test public void mockitoMapDemo1() { Map mockedMap = Mockito.mock(Map.class); when(mockedMap.get("city")).thenReturn("廣州"); Object cityValue = mockedMap.get("city"); assertThat(cityValue.toString(), is("廣州")); verify(mockedMap).get(Matchers.eq("city")); verify(mockedMap, times(2)); } @Test public void mockitoMapDemo2() { Map mockedMap = Mockito.mock(Map.class); // when(mockedMap.put(anyInt(), anyString())).thenReturn("world"); mockedMap.put(1, "hello"); verify(mockedMap).put(anyInt(), eq("hello")); } @Test public void mockitoListDemo() { List mockedList = Mockito.mock(List.class); mockedList.add("one"); mockedList.add("two"); verify(mockedList).add("one"); verify(mockedList, times(2)).add(anyString()); } }
JUnit 是單元測試框架。Mockito 與 JUnit 不一樣,並非單元測試框架(這方面 JUnit 已經足夠好了),它是用於生成模擬對象或者直接點說,就是」假對象「的工具。二者定位不一樣,因此通常一般的作法就是聯合 JUnit + Mockito 來進行測試。html
首先是配置 Mock 對象,看看例子怎麼寫的。java
List mockedList = Mockito.mock(List.class); when(mockedList.get(0)).thenReturn(1); assertEquals("Descriptive information ", 1, mockedList.get(0));
其中 mock 是模擬 List 的對象,擁有 List 的全部方法和屬性。when(xxxx).thenReturn(yyyy); 是指定當執行了這個方法的時候,返回 thenReturn 的值,至關因而對模擬對象的配置過程,爲某些條件給定一個預期的返回值。相信經過這個簡單的例子你能夠明白所謂 Mock 即是這麼一回事。正則表達式
咱們看到 List 爲 Java.util.List 是接口,並非實現類,但這不妨礙咱們使用它做爲咱們的「打樁」對象,——固然你也可使用實現類,傳入 mock(obj) 方法中。
這裏提到的是"打樁(Stub,也有人稱其爲「存根」)"的概念,是一個形象的說法,就是把所需的測試數據塞進對象中,
適用於基於狀態的(state-based)測試,關注的是輸入和輸出。
Mockito 中 when(…).thenReturn(…) 這樣的語法來定義對象方法和參數(輸入),而後在 thenReturn 中指定結果(輸出)。此過程稱爲 Stub 打樁。
一旦這個方法被 stub 了,就會一直返回這個 stub 的值。api
打樁須要注意如下幾點:數組
mock 對象會覆蓋整個被 mock 的對象,所以沒有 stub 的方法只能返回默認值。又由於,咱們 mock 一個接口的時候,不少成員方法只是一個簽名,並無實現,這就要咱們手動寫出這些實現方法啦。典型地,咱們模擬一個 request 請求對象,你被測試的代碼中使用了 HttpSerevletRequest 什麼方法,就要寫出相應的實現方法!框架
這裏「打樁」以後,咱們執行 request.getParamter("foo") 就會返回 boo,若是不這樣設定,Mockito 就會返回默認的 null,也不會報錯說這個方法找不到。
mock 實例默認的會給全部的方法添加基本實現:返回 null 或空集合,或者 0 等基本類型的值。
這取決於方法返回類型,如 int 會返回 0,布爾值返回 false。對於其餘 type 會返回 null。ide
打樁支持迭代風格的返回值設定,例如,工具
上述咱們一直在討論被測試的方法都有返回值的,那麼沒有返回值的 void 方法呢?也是測試嗎?答案是確定的。——只不過 Mockito 要求你的寫法上有不一樣,由於都沒返回值了,調用 thenReturn(xxx) 確定不行,取而代之的寫法是,學習
Mockito 還能對被測試的方法強行拋出異常,
如需指定異常類型,參見這裏。
拿上面的例子說,其中一個問題,
這裏 getParameter("foo") 這裏咱們是寫死參數 foo 的,可是若是我不關心輸入的具體內容,能夠嗎?能夠的,最好能像正則表達式那樣,/w+ 表示任意字符串是否是很方便,不用考慮具體什麼參數,只要是 字符串 型的參數,就能夠打樁。如此方便的想法 Mockito 也考慮到了,提供 argument matchers 機制,例如 anyString() 匹配任何 String 參數,anyInt() 匹配任何 int 參數,anySet() 匹配任何 Set,any() 則意味着參數爲任意值。例子以下,
when(mockedList.get(anyInt())).thenReturn("element");
System.out.println(mockedList.get(999));// 此時打印是 element
再進一步,自定義類型也能夠,如 any(User.class),另,參見《學習 Mockito - 自定義參數匹配器》 和 這裏 和 這裏。
一個問題,thenReturn 是返回結果是咱們寫死的。若是要讓被測試的方法不寫死,返回實際結果並讓咱們能夠獲取到的——怎麼作呢?
有時咱們須要自定義方法執行的返回結果,Answer 接口就是知足這樣的需求而存在的。
例如模擬常見的 request.getAttribute(key),因爲這原本是個接口,因此連內部實現都要本身寫了。這次經過 Answer 接口獲取參數內容。
利用 InvocationOnMock 提供的方法能夠獲取 mock 方法的調用信息。下面是它提供的方法:
void 方法能夠獲取參數,只是寫法上有區別,
其實就是一個回調,——若是不是接口,是實現類的話,估計不用本身寫實現。
前面提到的 when(……).thenReturn(……) 屬於狀態測試,某些時候,測試不關心返回結果,而是側重方法有否被正確的參數調用過,這時候就應該使用 驗證方法了。從概念上講,就是和狀態測試所不一樣的「行爲測試」了。
一旦使用 org.mockito.Mockito.mock() 對模擬對象打樁,意味着 Mockito 會記錄着這個模擬對象調用了什麼方法,還有調用了多少次。
最後由用戶決定是否須要進行驗證,即 org.mockito.Mockito.verify() 方法。
verify() 說明其做用的例子:
List mockedList = Mockito.mock(List.class); mockedList.add("one"); mockedList.add("two"); verify(mockedList).add("one");// 若是times不傳入,則默認是1 verify(mockedList, times(2)).add(anyString());
Map mockedMap = Mockito.mock(Map.class); when(mockedMap.get("city")).thenReturn("廣州"); Object cityValue = mockedMap.get("city"); assertThat(cityValue.toString(), is("廣州")); // 關注參數有否傳入 verify(mockedMap).get(Matchers.eq("city")); // 關注調用的次數 verify(mockedMap, times(2));
也就是說,這是對歷史記錄做一種回溯校驗的處理。
這裏補充一個學究的問題,所謂 Mock 與 Stub 打樁,其實它們之間不能互爲其表。但 Mockito 語境中則 Stub 和 Mock 對象同時使用的。由於它既能夠設置方法調用返回值,又能夠驗證方法的調用。有關 stub 和 mock 的詳細論述請見 Martin Fowler 大叔的文章《Mocks Aren't Stub》。
Mockito 除了提供 times(N) 方法供咱們調用外,還提供了不少可選的方法:
verify 也能夠像 when 那樣使用模擬參數,若方法中的某一個參數使用了matcher,則全部的參數都必須使用 matcher。
Map mockedMap = Mockito.mock(Map.class); mockedMap.put("", ""); String newValue = "newValue"; String oldValue = "oldValue"; //若方法中的某一個參數使用了matcher,則全部的參數都必須使用 matcher. // 不然會報異常:org.mockito.exceptions.misusing.InvalidUseOfMatchersException: // When using matchers, all arguments have to be provided by matchers. when(mockedMap.put(anyInt(), eq(newValue))).thenReturn(oldValue);//mock a put operation Object oldValueForPut = mockedMap.put(1, newValue);//get the mock value assertThat(oldValueForPut.toString(), is(oldValue));//assert the mock operation verify(mockedMap).put(anyInt(), eq(newValue));//verify whether the mock operation execute or not mockedMap.put(1, "hello"); verify(mockedMap).put(anyInt(), eq("hello"));
其餘高級用法,詳見《學習 Mockito - Mock對象的行爲驗證》,主要特性以下,
參見《用mockito的verify來驗證mock的方法是否被調用》:
看mockito的api時,一直都不清楚veriry()這個方法的做用,由於若是我mock了某個方法,確定是爲了調用的啊。直到今天在迴歸接口測試用例的時候,發現有兩個用例,用例2比用例1多了一個 mock 的步驟,不過最後的結果輸出是同樣的。因爲代碼作了修改,我從新 mock 後,其實用例2中對於的步驟是不會執行的,可測試仍是經過了。仔細查看後,發現mock的方法沒有被調用,因此用例2和用例1就變成同樣的了。因而,就產生了這麼個需求:單單經過結果來判斷正確與否仍是不夠的,我還要判斷是否按我指定的路徑執行的用例。到這裏,終於領略到了mockito的verify的強大威力,如下是示例代碼:
若調用成功,則程序正常運行,反之則會報告: Wanted but not invoked:verify(mockedList).add("one"); 錯誤。
感受 verify 會用的比較少。
spy 的意思是你能夠修改某個真實對象的某些方法的行爲特徵,而不改變他的基本行爲特徵,這種策略的使用跟 AOP 有點相似。下面舉官方的例子來講明:
能夠看到 spy 保留了 list 的大部分功能,只是將它的 size() 方法改寫了。不過 spy 在使用的時候有不少地方須要注意,一不當心就會致使問題,因此不到萬不得已仍是不要用 spy。
出處在這裏。
若是沒有 JUnit,可使用 Mockito 的 @Before 的註解,進行一些前期的初始化工做,
若是有 JUnit,則無需 @Before,但要修改 JUnit 默認容器,
在 JUnit 中有不少個 Runner ,他們負責調用你的測試代碼,每個 Runner 都有各自的特殊功能,你要根據須要選擇不一樣的 Runner 來運行你的測試代碼。
----------------------------------------------------
貌似 Mockito 的註解都比較強大,有待之後再看看:
《學習Mockito - Mockito對Annotation的支持》, http://jilen.iteye.com/blog/1427368
參見資源:
1自動生成Mock類
在須要Mock的屬性上標記@Mock註解,而後@RunWith(MockitoJUnitRunner.class)或者在setUp()方法中顯示調用MockitoAnnotations.initMocks(this);生成Mock類便可。
3 Mock方法定製不再用錄製、播放了
Mockito的Mock方法定製可讀性很強,並且也不須要像EasyMock那樣錄製播放,定製後就可使用。
例如:
when(userDao.selectAll()).
thenReturn(Collections.<UserDomain>emptyList());
http://blog.csdn.net/dc_726/article/details/8568537