Mockito(官網:http://site.mockito.org/)框架
一 mockito基本概念工具
Mock測試是單元測試的重要方法之一,而Mockito做爲一個流行的Mock框架,簡單易學,且有很是簡潔的API,測試代碼的可讀性很高。單元測試
Mock測試就是在測試過程當中,對於一些不容易構造(如HttpServletRequest必須在Servlet容器中才能構造出來)或者說獲取比較複雜的對象(如JDBC中的ResultSet對象)或者說咱們並不須要關注的對象,用一個虛擬的對象(Mock對象)來建立方便測試的測試方法。測試
Mock最大的功能是能夠幫咱們把單元測試的耦合分解開,若是代碼中對另外一個類或接口有依賴,它就能幫你模擬這些依賴,並幫你驗證所調用的依賴的行爲。xml
Java中目前主要的Mock測試工具備Mockito,JMock,EasyMock等等,不少Java Mock庫如EasyMock或JMock都是expect-run-verify(指望-運行-測試)的方式,而Mockito則更簡單:在執行後的互動中提問。使用Mockito主要記住,在執行前stub,然後在交互中驗證便可。對象
那麼什麼是stub呢?瞭解一下Stub和Mock的區別。繼承
Stub對象用來提供測試時所須要的測試數據,能夠對各類交互設置相應的迴應,好比說設置方法調用的返回值等等。咱們在Mockito中能夠經過when(...)thenReturn(...)來設置方法調用的返回值。接口
Mock對象用來驗證測試中所依賴的對象間的交互可否達到預期,咱們能夠在Mockito中用verify(...)methodXxx(...)語法來驗證methodXxx方法是否按預期被調用。它也有限制,對於final類、匿名類和Java的基本類型是沒法mock的。ci
二 具體使用rem
1.若是項目是Maven管理的,那麼就要在pom.xml中加入依賴:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.8.5</version> <scope>test</scope> </dependency>
而後在程序中直接import static org.mockito.Mockito.*; 便可
2.首先看一個例子:
List mockList = mock( List.class ); when( mockList .get(0) ).thenReturn( 1 ); assertEquals( "預期返回1", 1, mockList .get( 0 ) );
mockList模擬了List的對象,擁有List的全部方法和屬性。when(...).thenReturn(...)是指當執行這個方法的時候,返回指定的值。至關於模擬配置對象的過程,爲某些條件給定一個預期的返回值。
這裏的List是Java.util.List接口,並非實現類,你也可使用實現類,咱們可使用它們做爲打樁對象。這裏的打樁(Stub)也能夠叫存根,就是把所需的測試數據塞進對象中,關注的是輸入和輸出。這裏的when(...).thenReturn(...)就是在定義對象方法和參數(輸入),而後在thenReturn()中指定結果(輸出),這個過程就是Stub打樁,一旦這個方法被Stub了,就會一直返回這個stub的值。當咱們連續兩次爲同一個方法使用stub的時候,最後的那個stub是有效的。
一旦mock了一個對象以後,mock對象會覆蓋掉整個被mock的對象,所以若是沒有stub方法,就只能返回默認值。當咱們mock一個接口時,不少成員方法只是一個簽名,並無實現,須要咱們手動寫出這些實現方法。好比說,咱們模擬request請求對象,被測試的代碼中使用了HttpServletRequest的什麼方法,就要寫出相應的實現方法:
HttpServletRequest request = mock(HttpServletRequest.class); when(request.getParameter("foo")).thenReturn("boo");
若是咱們不經過when().thenReturn()返回預期值,mockito就會默認返回null,也不會報錯說這個方法找不到。mock實例默認的會給全部的方法添加基本實現:返回null或者空集合,或者0等基本類型的值。
3.迭代風格
打樁支持迭代風格的返回值設定,如:
// 第一種方式 when(i.next()).thenReturn("Hello").thenReturn("World"); // 第二種方式 when(i.next()).thenReturn("Hello", "World"); // 第三種方式,都是等價的 when(i.next()).thenReturn("Hello"); when(i.next()).thenReturn("World");
4.測試無返回值的方法
若是被測試的方法沒有返回值,那麼測試方法是:
doNothing().when(i).remove(); doNothing().when(obj).notify(); // 或直接 when(obj).notify();
5.拋出異常
mockito還能對被測試的方法強行拋出異常:
when(i.next()).thenThrow(new RuntimeException()); doThrow(new RuntimeException()).when(i).remove();
支持迭代風格:
doNothing().doThrow(new RuntimeException()).when(i).remove(); //第一次調用remove方法什麼都不作,第二次調用拋出RuntimeException異常。
6.參數匹配器
Argument Matcher(參數匹配器)
Mockito經過equals()方法,來對方法參數進行驗證。可是有時候咱們須要更加靈活的參數需求,好比,匹配任何的String類型的參數等等。參數匹配器就是一個可以知足這些需求的工具。
Mockito框架中的Matchers類內建了不少參數匹配器,咱們經常使用的Mockito對象就是繼承自Matchers。好比anyInt()匹配任何int類型的參數,anyString()匹配任何字符串...
@Test public void argumentMatchersTest(){ List<String> mock = mock(List.class); when(mock.get(anyInt())).thenReturn("Hello").thenReturn("World"); String result=mock.get(100)+" "+mock.get(200); verify(mock,times(2)).get(anyInt()); assertEquals("Hello World",result); }
首先mock了List接口,而後用迭代的方式模擬了get方法的返回值,這裏用了anyInt()參數匹配器來匹配任何的int類型的參數。因此當第一次調用get方法時輸入任意參數爲100方法返回」Hello」,第二次調用時輸入任意參數200返回值」World」。
這裏須要注意:
若是使用了參數匹配器,那麼全部的參數須要由匹配器來提供,不然將會報錯。假如咱們使用參數匹配器stubbing了mock對象的方法,那麼在verify的時候也須要使用它。如:
@Test public void argumentMatchersTest(){ Map mapMock = mock(Map.class); when(mapMock.put(anyInt(), anyString())).thenReturn("world"); mapMock.put(1, "hello"); verify(mapMock).put(anyInt(), eq("hello")); }
在最後的驗證時若是隻輸入字符串」hello」是會報錯的,必須使用Matchers類內建的eq方法。若是將anyInt()換成1進行驗證也須要用eq(1)。
7.驗證Verify
以前的when(...).thenReturn(...)屬於狀態測試,有些時候,測試並不關心返回結果,而是關心方法是否被正確的參數調用過,這時候就應該使用驗證方法了。從概念上講,就是和狀態測試不一樣的「行爲測試」了。一旦經過mock對模擬對象打樁,意味着mockito會記錄着這個模擬對象調用了什麼方法,調用了多少次,最後由用戶決定是否須要進行驗證,即verify()方法。
mockedList.add("one"); mockedList.add("two"); verify(mockedList).add("one");
verify 內部跟蹤了全部的方法調用和參數的調用狀況,而後會返回一個結果,說明是否經過。
Map mock = Mockito.mock( Map.class ); when( mock.get( "city" ) ).thenReturn( "廣州" ); // 關注參數有否傳入 verify(mock).get( Matchers.eq( "city" ) ); // 關注調用的次數 verify(mock, times( 2 ));
也就是說,這是對歷史記錄做一種回溯校驗的處理。
Mockito 除了提供 times(N) 方法供咱們調用外,還提供了不少可選的方法:
never() 沒有被調用,至關於 times(0)
atLeast(N) 至少被調用 N 次
atLeastOnce() 至關於 atLeast(1)
atMost(N) 最多被調用 N 次
verify 也能夠像 when 那樣使用模擬參數,若方法中的某一個參數使用了matcher,則全部的參數都必須使用 matcher。
// correct verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); // will throw exception verify(mock).someMethod(anyInt(), anyString(), "third argument");
8.Spy
Mock對象是能調用stubbed方法,調用不了它真實的方法,可是Mockito能夠監視一個真實的對象,這時對它進行方法調用時它將調用真實的方法,同時也能夠stubbing這個對象的方法讓它返回咱們的指望值。同時,咱們也能夠用verify進行驗證。
監視對象
監視一個對象須要調用spy(T object)方法,如:List spy = spy(new LinkedList());那麼spy變量就在監視LinkedList實例。
被監視對象的Stubbing
stubbing被監視對象的方法時要慎用when(Object),如:
List spy = spy(new LinkedList()); //Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty) when(spy.get(0)).thenReturn("foo"); //You have to use doReturn() for stubbing doReturn("foo").when(spy).get(0);
當調用when(spy.get(0)).thenReturn("foo")時,會調用真實對象的get(0),因爲list是空的因此會拋出IndexOutOfBoundsException異常,用doReturn能夠避免這種狀況的發生,由於它不會去調用get(0)方法。
9.使用ArgumentCaptor(參數捕獲器) 捕獲方法參數進行驗證
在某些場景中,不光要對方法的返回值和調用進行驗證,同時須要驗證一系列交互後所傳入方法的參數,這時咱們能夠用參數捕獲器來捕獲傳入方法的參數進行驗證,看它是否符合咱們的要求。
經過ArgumentCaptor對象的forClass(Class<T> clazz)方法來構建ArgumentCaptor對象而後就能夠在驗證時對方法的參數進行捕獲,最後驗證捕獲的參數值。若是方法有多個參數都要捕獲驗證,那就須要建立多個ArgumentCaptor對象處理。
argument.capture() 捕獲方法參數
argument.getValue() 獲取方法參數值,若是方法進行了屢次調用,它將返回最後一個參數值
argument.getAllValues() 方法進行屢次調用後,返回多個參數值
@Test public void argumentCaptorTest() { List mock = mock(List.class); List mock2 = mock(List.class); mock.add("John"); mock2.add("Brian"); mock2.add("Jim"); ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); verify(mock).add(argument.capture()); assertEquals("John", argument.getValue()); verify(mock2, times(2)).add(argument.capture()); assertEquals("Jim", argument.getValue()); //assertArrayEquals(new Object[]{"Brian","Jim"},argument.getAllValues().toArray()); //注意按照上面原文代碼進行編寫,執行的junit test結果是異常,由於只有一個argumentCaptor對象,經過capture()方法獲取的是三個參數,按照下面的代碼,執行的junit test結果是相同(即經過)。 assertArrayEquals(new Object[]{"Jhon","Brian","Jim"},argument.getAllValues().toArray()); }
在某種程度上參數捕獲器和參數匹配器有很大的相關性。它們都用來確保傳入mock對象參數的正確性。然而,當自定義的參數匹配器的重用性較差時,用參數捕獲器會更合適,只需在最後對參數進行驗證便可。