安卓單元測試Mockito的使用

Android Mockito 使用目錄

簡單的例子

Mockito - mock返回值

Mockito - 參數匹配器

Mockito - 驗證調用次數

Mockito - 驗證順序

Mockito - 保模與其他mock未發生相互影響

Mockito - 找出冗餘的調用

Mockito - 使用@Mock註解

Mockito - 使用迭代器方式驗證我們的參數

Mockito - spy (間諜)

Mockito - 重置

Mockito - BDD 測試

Mokito -  Verification with timeout

Mockito - 自定義驗證失敗消息

Mockito - when 的使用

Mockito - verify的使用

Mockito - 一些其他的API

doNothing

doReturn

inOrder

ignoreStubs

times

never

atLeastOnce

atLeast

atMost

calls

only

timeout

after


Mockito - Android支持

在Mockito 2.6.1版中,提供了“本機” Android支持。要啓用Android支持,請將`mockito-android`庫作爲依賴項添加到我們的項目中。該工件已發佈到同一Mockito組織,並且可以如下導入Android:

 repositories {
   jcenter()
 }
 dependencies {
   testImplementation "org.mockito:mockito-core:+"
   androidTestImplementation "org.mockito:mockito-android:+"
 }

我們可以通過在“ testImplementation”範圍內使用“ mockito-core”庫,繼續在常規VM上運行相同的單元測試,如上所示。請注意,由於Android VM中的限制,我們不能在Android上使用Mockito,但是如果我們想要使用的話那麼需要倒入“mockito-android”。

簡單的例子

 //Let's import Mockito statically so that the code looks clearer
 import static org.mockito.Mockito.*;//mock creation
 
 List mockedList = mock(List.class);
 //using mock object
 mockedList.add("one");
 mockedList.clear();
 //verification List add() method called and param is "one"
 verify(mockedList).add("one");
 //verification List clear() method called 
 verify(mockedList).clear();

一旦創建,模擬程序將會記住所有的交互。然後我們就可以有選擇的驗證我們感興趣的任何事情。

Mockito - mock返回值


//You can mock concrete classes, not just interfaces
 LinkedList mockedList = mock(LinkedList.class);
 //stubbing
 when(mockedList.get(0)).thenReturn("first");
 when(mockedList.get(1)).thenThrow(new RuntimeException());
 //following prints "first"
 System.out.println(mockedList.get(0));
 //following throws runtime exception
 System.out.println(mockedList.get(1));
 //following prints "null" because get(999) was not stubbed
 System.out.println(mockedList.get(999));
 //雖然可以驗證存根調用,但通常只是多餘的
 //如果我們的代碼關心get(0)返回的內容,則其他內容將中斷(甚至在執行verify()之前)。
 //如果我們的代碼不關心get(0)返回的內容,則不應將其存根。不服氣嗎?看這裏。
 verify(mockedList).get(0);

 

  • 默認情況下,對於所有返回值的方法,Mockito將根據需要返回null,原始/原始包裝器值或空集合。例如,對於int / Integer爲0,對於boolean / Boolean爲false。

  • 存根可以被覆蓋:例如,通用存根可以正常設置,但是測試方法可以覆蓋它。請注意,過多的存根使代碼過於難看和不優雅,這表明存在過多的存根。

  • 一旦存根,該方法將始終返回存根值,而不管其被調用了多少次。

  • 最後一次存根更爲重要-當我們多次對具有相同參數的相同方法進行存根時。換句話說:存根的順序很重要,但很少有意義,例如,存根完全相同的方法調用時或有時使用參數匹配器時,等等。

Mockito - 參數匹配器

Mockito通過使用一種 equals() 方法來驗證自然Java風格的參數值。有時,當需要額外的靈活性時,我們可以使用參數匹配器:

//stubbing using built-in anyInt() argument matcher
 when(mockedList.get(anyInt())).thenReturn("element");
 //stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
 when(mockedList.contains(argThat(isValid()))).thenReturn("element");
 //following prints "element"
 System.out.println(mockedList.get(999));
 //you can also verify using an argument matcher
 verify(mockedList).get(anyInt());
 //argument matchers can also be written as Java 8 Lambdas
 verify(mockedList).add(argThat(someString -> someString.length() > 5));

參數匹配器允許靈活的驗證或存根。 查看更多內置匹配器以及自定義參數匹配器參考此文檔有關僅自定義參數匹配器的信息,請查看javadoc中的ArgumentMatcher類。使用複雜的參數匹配是合理的。將equals()與 anyX()匹配器配合使用的自然匹配樣式傾向於提供簡潔的測試。有時最好重構代碼以允許equals()匹配,甚至實現equals()方法來幫助進行測試。

關於參數匹配器的注意事項

如果使用參數匹配器,則必須由匹配器提供所有參數。

以下示例顯示了驗證,但對存根也是如此:

verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
//這是個正確的例子因爲eq()是一個Matcher
verify(mock).someMethod(anyInt(), anyString(), "third argument");
//這是個錯誤的例子會拋出一個異常因爲  "third argument" 不是一個 Matcher 類

諸如anyObject(),eq()之類的匹配器方法不會返回匹配器。在內部,它們在堆棧上記錄一個匹配器,並返回一個虛擬值(通常爲null)。此實現歸因於Java編譯器施加的靜態類型安全性。結果是我們不能在經過驗證/存根的方法之外使用anyObject()和eq()方法。

Mockito - 驗證調用次數

 //using mock
 mockedList.add("once");
 mockedList.add("twice");
 mockedList.add("twice");
 mockedList.add("three times");
 mockedList.add("three times");
 mockedList.add("three times");
 //following two verifications work exactly the same - times(1) is used by default
 verify(mockedList).add("once");
 verify(mockedList, times(1)).add("once");
 //exact number of invocations verification
 verify(mockedList, times(2)).add("twice");
 verify(mockedList, times(3)).add("three times");
 //verification using never(). never() is an alias to times(0)
 verify(mockedList, never()).add("never happened");
 //verification using atLeast()/atMost()
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("three times");
 verify(mockedList, atMost(5)).add("three times");

默認爲times(1)。因此,可以顯式地省略使用times(1)。

Mockito - 驗證順序

 // A. Single mock whose methods must be invoked in a particular order
 List singleMock = mock(List.class);
 //using a single mock
 singleMock.add("was added first");
 singleMock.add("was added second");
 //create an inOrder verifier for a single mock
 InOrder inOrder = inOrder(singleMock);
 //following will make sure that add is first called with "was added first, then with "was added second"
 inOrder.verify(singleMock).add("was added first");
 inOrder.verify(singleMock).add("was added second");
 // B. Multiple mocks that must be used in a particular order
 List firstMock = mock(List.class);
 List secondMock = mock(List.class);
 //using mocks
 firstMock.add("was called first");
 secondMock.add("was called second");
 //create inOrder object passing any mocks that need to be verified in order
 InOrder inOrder = inOrder(firstMock, secondMock);
 //following will make sure that firstMock was called before secondMock
 inOrder.verify(firstMock).add("was called first");
 inOrder.verify(secondMock).add("was called second");
 // Oh, and A + B can be mixed together at will

按順序進行驗證是靈活的-我們不必一一驗證所有交互,而只需依次驗證我們感興趣的交互即可。同樣,我們可以創建一個InOrder對象,該對象僅傳遞與順序驗證相關的測試。

Mockito - 保模與其他mock未發生相互影響


//using mocks - only mockOne is interacted
 mockOne.a
 dd("one");

 //ordinary verification
 verify(mockOne).add("one");

 //verify that method was never called on a mock
 verify(mockOne, never()).add("two");

 //verify that other mocks were not interacted
 verifyZeroInteractions(mockTwo, mockThree);

Mockito - 找出冗餘的調用


//using mocks
 mockedList.add("one");
 mockedList.add("two");

 verify(mockedList).add("one");

 //following verification will fail
 verifyNoMoreInteractions(mockedList);
 

最好使用never()代替

Mockito - 使用@Mock註解

  • 減少重複的模擬創建代碼

  • 增加可讀性

  • 因爲字段名稱用於標識Mock,所以使驗證錯誤更易於閱讀。

下面我們看一下例子

public class ArticleManagerTest {

   @Mock 
   private ArticleCalculator calculator;
   @Mock 
   private ArticleDatabase database;
   @Mock 
   private UserProvider userProvider;

   private ArticleManager manager;
    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }
」

你可以使用MockitoJUnitRunner or a rule: MockitoRule.來運行這個測試類。

Mockito - 使用迭代器方式驗證我們的參數

有時,對於同一方法調用,我們需要對不同的返回值/異常進行存根。典型的用例可能是模擬迭代器。Mockito的原始版本沒有此功能來促進簡單的模擬。例如,可以使用Iterable或簡單地使用集合來代替迭代器。這些提供了自然的存根方式(例如使用真實集合)。在極少數情況下,對連續調用進行存根可能會很有用

List a = mock(List.class);
when(a.get(0)).thenReturn("1","2","3");
assertThat(a.get(0),is(equalTo("1")));
assertThat(a.get(0),is(equalTo("2")));
assertThat(a.get(0),is(equalTo("3")));

when(a.get(1)).thenReturn("1").thenReturn("2").thenReturn("3");
assertThat(a.get(1),is(equalTo("1")));
assertThat(a.get(1),is(equalTo("2")));
assertThat(a.get(1),is(equalTo("3")));

注意:如果使用多個具有相同匹配器或參數的存根而不是鏈接.thenReturn()調用,則每個存根將覆蓋前一個存根:

 //All mock.someMethod("some arg") calls will return "two"
 when(mock.someMethod("some arg"))
   .thenReturn("one")
 when(mock.someMethod("some arg"))
   .thenReturn("two")
 

Mockito - spy (間諜)

我們可以創建真實對象的間諜。當我們使用間諜時,將調用實際方法(除非對方法進行了加註)。真正的間諜應該小心謹慎地使用,例如在處理遺留代碼時。監視真實對象可以與“部分模擬”概念相關聯。在1.8版之前,Mockito間諜並不是真正的部分模擬。

   List list = new LinkedList();
   List spy = spy(list);

   //optionally, you can stub out some methods:
   when(spy.size()).thenReturn(100);

   //using the spy calls *real* methods
   spy.add("one");
   spy.add("two");

   //prints "one" - the first element of a list
   System.out.println(spy.get(0));

   //size() method was stubbed - 100 is printed
   System.out.println(spy.size());

   //optionally, you can verify
   verify(spy).add("one");
   verify(spy).add("two");

有時,使用when(Object)來偵探間諜是不可能或不切實際的。因此,在使用間諜程序時,請考慮使用doReturn | Answer | Throw()系列方法進行存根。例:

   List list = new LinkedList();
   List spy = spy(list);

   //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);
 

Mockito *不會*將調用委託給傳遞的真實實例,而是實際上創建它的副本。因此,如果保留真實實例並與之交互,請不要期望間諜知道這些交互及其對真實實例狀態的影響。必然的結果是,當在間諜*上調用*未使用*的方法,而在真實實例上*卻未調用*方法時,您將看不到任何對真實實例的影響。注意最終方法。Mockito不模擬最終方法,因此最重要的是:當您監視真實對象時+嘗試對最終方法進行存根=麻煩。另外,我們將無法驗證這些方法。

Mockito - 重置

聰明的Mockito用戶幾乎不使用此功能,因爲他們知道這可能是測試不佳的跡象。通常,您無需重置模擬,只需爲每個測試方法創建新的模擬即可。代替reset(),請考慮在冗長,過度指定的測試中編寫簡單,小型且集中的測試方法。在測試方法的中間,第一個潛在的代碼被reset()。這可能意味着您正在測試太多。請遵循您的測試方法的耳語:“請保持我們的注意力,並專注於單一行爲”。

   List mock = mock(List.class);
   when(mock.size()).thenReturn(10);
   mock.add(1);

   reset(mock);
   //at this point the mock forgot any interactions & stubbing

Mockito - BDD 測試

行爲驅動開發測試的風格使用//給予//註釋,然後將註釋作爲測試方法的基本組成部分。這正是我們編寫測試的方式,我們熱烈鼓勵這樣做!從此處開始瞭解有關BDD的信息:鏈接問題在於,當前的stub api具有規範的作用,即word不能與// then / then / then註釋很好地集成在一起。這是因爲存根屬於測試的給定組件,而不屬於測試的when組件。因此,BDDMockito類引入了一個別名,以便您可以使用BDDMockito.given(Object)方法對方法調用進行存根。現在,它可以很好地與BDD樣式測試的給定組件集成!這是測試的樣子:

 import static org.mockito.BDDMockito.*;

 Seller seller = mock(Seller.class);
 Shop shop = new Shop(seller);

 public void shouldBuyBread() throws Exception {
   //given
   given(seller.askForBread()).willReturn(new Bread());

   //when
   Goods goods = shop.buyBread();

   //then
   assertThat(goods, containBread());
 }
 

Mokito -  Verification with timeout

允許驗證超時。它使驗證等待指定的時間段以進行所需的交互,而不是如果尚未發生則立即失敗。在併發條件下進行測試可能有用。我們很少使用這個方法尚未實現與InOrder驗證一起使用。

   //passes when someMethod() is called within given time span
   verify(mock, timeout(100)).someMethod();
   //above is an alias to:
   verify(mock, timeout(100).times(1)).someMethod();

   //passes when someMethod() is called *exactly* 2 times within given time span
   verify(mock, timeout(100).times(2)).someMethod();

   //passes when someMethod() is called *at least* 2 times within given time span
   verify(mock, timeout(100).atLeast(2)).someMethod();

   //verifies someMethod() within given time span using given verification mode//useful only if you have your own custom verification modes.
   verify(mock, new Timeout(100, yourOwnVerificationMode)).someMethod();

Mockito - 自定義驗證失敗消息


// will print a custom message on verification failure
 verify(mock, description("This will print on failure")).someMethod();

 // will work with any verification mode
 verify(mock, times(2).description("someMethod should be called twice")).someMethod();

Mockito - Java 8 Lambda Matcher Support

您可以將Java 8 lambda表達式與ArgumentMatcher一起使用,以減少對ArgumentCaptor的依賴性。如果需要驗證對模擬函數調用的輸入是否正確,則通常可以使用ArgumentCaptor查找所使用的操作數,然後對它們進行後續聲明。雖然對於複雜的示例來說這可能是有用的,但也很費勁。編寫lambda很容易。與argThat一起使用時,函數的參數將作爲強類型對象傳遞給ArgumentMatcher,因此可以執行任何操作。


// verify a list only had strings of a certain length added to it// note - this will only compile under Java 8
 verify(list, times(2)).add(argThat(string -> string.length() < 5));

 // Java 7 equivalent - not as neat
 verify(list, times(2)).add(argThat(new ArgumentMatcher(){
     public boolean matches(String arg) {
         return arg.length() < 5;
     }
 }));

 // more complex Java 8 example - where you can specify complex verification behaviour functionally
 verify(target, times(1)).receiveComplexObject(argThat(obj -> obj.getSubObject().get(0).equals("expected")));

 // this can also be used when defining the behaviour of a mock under different inputs// in this case if the input list was fewer than 3 items the mock returns null
 when(mock.someMethod(argThat(list -> list.size()<3))).willReturn(null);

Mockito - when 的使用

啓用存根方法。當您希望模擬在調用特定方法時返回特定值時,請使用它。簡而言之:“當調用x方法時,返回y”。

Examples:

when(mock.someMethod()).thenReturn(10);

 //you can use flexible argument matchers, e.g:
 when(mock.someMethod(anyString())).thenReturn(10);

 //setting exception to be thrown:
 when(mock.someMethod("some arg")).thenThrow(new RuntimeException());

 //you can set different behavior for consecutive method calls.//Last stubbing (e.g: thenReturn("foo")) determines the behavior of further consecutive calls.
 when(mock.someMethod("some arg"))
  .thenThrow(new RuntimeException())
  .thenReturn("foo");

 //Alternative, shorter version for consecutive stubbing:
 when(mock.someMethod("some arg"))
  .thenReturn("one", "two");
 //is the same as:
 when(mock.someMethod("some arg"))
  .thenReturn("one")
  .thenReturn("two");

 //shorter version for consecutive method calls throwing exceptions:
 when(mock.someMethod("some arg"))
  .thenThrow(new RuntimeException(), new NullPointerException();

Mockito - verify的使用

verify(mock).someMethod("some arg");
//Above is equivalent to:
verify(mock, times(1)).someMethod("some arg");

驗證某些行爲至少發生一次/確切次數/永不發生。

  verify(mock, times(5)).someMethod("was called five times");

   verify(mock, atLeast(2)).someMethod("was called at least two times");

   //you can use flexible argument matchers, e.g:
   verify(mock, atLeastOnce()).someMethod(anyString());

Mockito - 一些其他的API

doNothing

   doNothing().
   doThrow(new RuntimeException())
   .when(mock).someVoidMethod();

   //does nothing the first time:
   mock.someVoidMethod();

   //throws RuntimeException the next time:
   mock.someVoidMethod();
 
 
 
   List list = new LinkedList();
   List spy = spy(list);

   //let's make clear() do nothing
   doNothing().when(spy).clear();

   spy.add("one");

   //clear() does nothing, so the list still contains "one"
   spy.clear();
 

 

使用doNothing()將void方法設置爲不執行任何操作。注意,默認情況下,模擬上的void方法什麼也不做!但是,在少數情況下,doNothing()會派上用場

doReturn

在無法使用when(Object)的極少數情況下,請使用doReturn()。請注意,始終建議對when(Object)進行存根,因爲它是參數類型安全的,並且更具可讀性(尤其是在存根連續調用時)。以下是doReturn()派上用場的那些罕見情況:

監視真實對象並在間諜上調用真實方法時會帶來副作用

   List list = new LinkedList();
   List spy = spy(list);

   //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);
   
   
   //Overriding a previous exception-stubbing
   when(mock.foo()).thenThrow(new RuntimeException());
  
    //Impossible: the exception-stubbed foo() method is called so RuntimeException is thrown.
   when(mock.foo()).thenReturn("bar");

   //You have to use doReturn() for stubbing:
   doReturn("bar").when(mock).foo();
   
   List list = new LinkedList();
   List spy = spy(list);

   //Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
   when(spy.get(0)).thenReturn("foo", "bar", "qix");

   //You have to use doReturn() for stubbing:
   doReturn("foo", "bar", "qix").when(spy).get(0);
     
      
   when(mock.foo()).thenThrow(new RuntimeException());

   //Impossible: the exception-stubbed foo() method is called so RuntimeException is thrown.
   when(mock.foo()).thenReturn("bar", "foo", "qix");

   //You have to use doReturn() for stubbing:
   doReturn("bar", "foo", "qix").when(mock).foo();
 

以上方案顯示了Mockito優雅語法的折中方案。請注意,這種情況很少見。間諜活動應該是零星的,並且很少使用異常處理。更不用說總的來說,過度存根是一種潛在的代碼異味,它指出了太多的存根。

inOrder

   InOrder inOrder = inOrder(firstMock, secondMock);

   inOrder.verify(firstMock).add("was called first");
   inOrder.verify(secondMock).add("was called second");

ignoreStubs

//mocking lists for the sake of the example (if you mock List in real you will burn in hell)
  List mock1 = mock(List.class), mock2 = mock(List.class);

  //stubbing mocks:
  when(mock1.get(0)).thenReturn(10);
  when(mock2.get(0)).thenReturn(20);

  //using mocks by calling stubbed get(0) methods:
  System.out.println(mock1.get(0)); //prints 10
  System.out.println(mock2.get(0)); //prints 20//using mocks by calling clear() methods:
  mock1.clear();
  mock2.clear();

  //verification:
  verify(mock1).clear();
  verify(mock2).clear();

  //verifyNoMoreInteractions() fails because get() methods were not accounted for.try { verifyNoMoreInteractions(mock1, mock2); } catch (NoInteractionsWanted e);

  //However, if we ignore stubbed methods then we can verifyNoMoreInteractions()
  verifyNoMoreInteractions(ignoreStubs(mock1, mock2));

  //Remember that ignoreStubs() *changes* the input mocks and returns them for convenience.
  
  List list = mock(List.class);
  when(mock.get(0)).thenReturn("foo");

  list.add(0);
  System.out.println(list.get(0)); //we don't want to verify this
  list.clear();

  InOrder inOrder = inOrder(ignoreStubs(list));
  inOrder.verify(list).add(0);
  inOrder.verify(list).clear();
  inOrder.verifyNoMoreInteractions();

忽略存根可以按順序用於驗證

times

Allows verifying exact number of invocations. E.g:

verify(mock, times(2)).someMethod("some arg");

never

Verifies that interaction did not happen. E.g:


   verify(mock, never()).someMethod();
 

atLeastOnce

verification modeatLeastOnce

verification. E.g:

verify(mock, atLeastOnce()).someMethod("some arg");

atLeast

verification. E.g:

verify(mock, atLeast(3)).someMethod("some arg");

atMost

E.g:

  verify(mock, atMost(3)).someMethod("some arg");

calls

For example


   inOrder.verify( mock, calls( 2 )).someMethod( "some arg" );
 

only

E.g:


   verify(mock, only()).someMethod();
   //above is a shorthand for following 2 lines of code:
   verify(mock).someMethod();
   verifyNoMoreInvocations(mock);
 

timeout


   //passes when someMethod() is called within given time span
   verify(mock, timeout(100)).someMethod();
   //above is an alias to:
   verify(mock, timeout(100).times(1)).someMethod();

   //passes as soon as someMethod() has been called 2 times before the given timeout
   verify(mock, timeout(100).times(2)).someMethod();

   //equivalent: this also passes as soon as someMethod() has been called 2 times before the given timeout
   verify(mock, timeout(100).atLeast(2)).someMethod();

   //verifies someMethod() within given time span using given verification mode
   //useful only if you have your own custom verification modes.
   verify(mock, new Timeout(100, yourOwnVerificationMode)).someMethod();
 

after


   //passes after 100ms, if someMethod() has only been called once at that time.
   verify(mock, after(100)).someMethod();
   //above is an alias to:
   verify(mock, after(100).times(1)).someMethod();

   //passes if someMethod() is called *exactly* 2 times after the given timespan
   verify(mock, after(100).times(2)).someMethod();

   //passes if someMethod() has not been called after the given timespan
   verify(mock, after(100).never()).someMethod();

   //verifies someMethod() after a given time span using given verification mode
   //useful only if you have your own custom verification modes.
   verify(mock, new After(100, yourOwnVerificationMode)).someMethod();
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章