Mockito測試(一)

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對象參數的正確性。然而,當自定義的參數匹配器的重用性較差時,用參數捕獲器會更合適,只需在最後對參數進行驗證便可。

相關文章
相關標籤/搜索