Mockito

單元測(cè)試的目標(biāo)和挑戰(zhàn)

單元測(cè)試的思路是在不涉及依賴(lài)關(guān)系的情況下測(cè)試代碼(隔離性)疯搅,所以測(cè)試代碼與其他類(lèi)或者系統(tǒng)的關(guān)系應(yīng)該盡量被消除嚷狞。

一個(gè)可行的消除方法是替換掉依賴(lài)類(lèi)(測(cè)試替換)欧漱,也就是說(shuō)我們可以使用替身來(lái)替換掉真正的依賴(lài)對(duì)象肩豁。

mock測(cè)試

Mock 測(cè)試就是在測(cè)試過(guò)程中侍芝,對(duì)于某些不容易構(gòu)造(如 HttpServletRequest 必須在Servlet 容器中才能構(gòu)造出來(lái))或者不容易獲取比較復(fù)雜的對(duì)象(如 JDBC 中的ResultSet 對(duì)象)箱锐,用一個(gè)虛擬的對(duì)象(Mock 對(duì)象)來(lái)創(chuàng)建以便測(cè)試的測(cè)試方法比勉。

Mock 最大的功能是幫你把單元測(cè)試的耦合分解開(kāi),如果你的代碼對(duì)另一個(gè)類(lèi)或者接口有依賴(lài)驹止,它能夠幫你模擬這些依賴(lài)浩聋,并幫你驗(yàn)證所調(diào)用的依賴(lài)的行為。

比如一段代碼有這樣的依賴(lài):



當(dāng)我們需要測(cè)試A類(lèi)的時(shí)候臊恋,如果沒(méi)有 Mock衣洁,則我們需要把整個(gè)依賴(lài)樹(shù)都構(gòu)建出來(lái),而使用 Mock 的話(huà)就可以將結(jié)構(gòu)分解開(kāi)抖仅,像下面這樣:


Mock 對(duì)象使用范疇

真實(shí)對(duì)象具有不可確定的行為坊夫,產(chǎn)生不可預(yù)測(cè)的效果(如:天氣預(yù)報(bào)) :

  • 真實(shí)對(duì)象很難被創(chuàng)建的
  • 真實(shí)對(duì)象的某些行為很難被觸發(fā)
  • 真實(shí)對(duì)象實(shí)際上還不存在的(和其他開(kāi)發(fā)小組或者和新的硬件打交道)等等

關(guān)鍵步驟

  • 使用一個(gè)接口來(lái)描述這個(gè)對(duì)象
  • 在產(chǎn)品代碼中實(shí)現(xiàn)這個(gè)接口
  • 在測(cè)試代碼中實(shí)現(xiàn)這個(gè)接口
  • 在被測(cè)試代碼中只是通過(guò)接口來(lái)引用對(duì)象砖第,所以它不知道這個(gè)引用的對(duì)象是真實(shí)對(duì)象,還是 Mock 對(duì)象环凿。

用Mock測(cè)試你的代碼

測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)(Test Driven Design, TDD)要求我們先寫(xiě)單元測(cè)試梧兼,再寫(xiě)實(shí)現(xiàn)代碼。在寫(xiě)單元測(cè)試的過(guò)程中智听,一個(gè)很普遍的問(wèn)題是羽杰,要測(cè)試的類(lèi)會(huì)有很多依賴(lài),這些依賴(lài)的類(lèi)/對(duì)象/資源又會(huì)有別的依賴(lài)到推,從而形成一個(gè)大的依賴(lài)樹(shù)考赛,要在單元測(cè)試的環(huán)境中完整地構(gòu)建這樣的依賴(lài),是一件很困難的事情莉测。

所幸颜骤,我們有一個(gè)應(yīng)對(duì)這個(gè)問(wèn)題的辦法:Mock。簡(jiǎn)單地說(shuō)就是對(duì)測(cè)試的類(lèi)所依賴(lài)的其他類(lèi)和對(duì)象捣卤,進(jìn)行mock - 構(gòu)建它們的一個(gè)假的對(duì)象复哆,定義這些假對(duì)象上的行為,然后提供給被測(cè)試對(duì)象使用腌零。被測(cè)試對(duì)象像使用真的對(duì)象一樣使用它們梯找。用這種方式,我們可以把測(cè)試的目標(biāo)限定于被測(cè)試對(duì)象本身益涧,就如同在被測(cè)試對(duì)象周?chē)隽艘粋€(gè)劃斷锈锤,形成了一個(gè)盡量小的被測(cè)試目標(biāo)。

Mock的框架有很多闲询,最為知名的一個(gè)是Mockito久免,這是一個(gè)開(kāi)源項(xiàng)目,使用廣泛扭弧。

Java Mock 測(cè)試

目前阎姥,在 Java 陣營(yíng)中主要的 Mock 測(cè)試工具有 MockitoJMock鸽捻,EasyMock 等呼巴。

Mockito 特性

  • Mockito 是美味的 Java 單元測(cè)試 Mock 框架。
  • 大多 Java Mock 庫(kù)如 EasyMock 或 JMock 都是 expect-run-verify (期望-運(yùn)行-驗(yàn)證)方式,你常常被迫查看無(wú)關(guān)的交互
  • Mockito 則使用更簡(jiǎn)單御蒲,更直觀的方法:在執(zhí)行后的互動(dòng)中提問(wèn)衣赶。
  • Mockito 無(wú)需準(zhǔn)備昂貴的前期啟動(dòng)。他們的目標(biāo)是透明的厚满,讓開(kāi)發(fā)人員專(zhuān)注于測(cè)試選定的行為府瞄。
  • Mockito 擁有的非常少的 API,所有開(kāi)始使用 Mockito碘箍,幾乎沒(méi)有時(shí)間成本遵馆。因?yàn)橹挥幸环N創(chuàng)造 mock 的方式鲸郊。只要記住,在執(zhí)行前 stub货邓,而后在交互中驗(yàn)證秆撮。你很快就會(huì)發(fā)現(xiàn)這樣 TDD java 代碼是多么自然。
  • 可以 mock 具體類(lèi)而不單止是接口
  • 一點(diǎn)注解語(yǔ)法糖 - @Mock
  • 干凈的驗(yàn)證錯(cuò)誤是 - 點(diǎn)擊堆棧跟蹤逻恐,看看在測(cè)試中的失敗驗(yàn)證;點(diǎn)擊異常的原因來(lái)導(dǎo)航到代碼中的實(shí)際互動(dòng)。堆棧跟蹤總是干干凈凈峻黍。
  • 允許靈活有序的驗(yàn)證(例如:你任意有序 verify复隆,而不是每一個(gè)單獨(dú)的交互)
  • 支持“詳細(xì)的用戶(hù)號(hào)碼的時(shí)間”以及“至少一??次”驗(yàn)證
  • 靈活的驗(yàn)證或使用參數(shù)匹配器的 stub (anyObject()anyString()refEq() 用于基于反射的相等匹配)
  • 允許創(chuàng)建自定義的參數(shù)匹配器或者使用現(xiàn)有的 hamcrest 匹配器

先睹為快

// 創(chuàng)建mock對(duì)象
List mockedList = Mockito.mock(List.class);

// 設(shè)置mock對(duì)象的行為 - 當(dāng)調(diào)用其get方法獲取第0個(gè)元素時(shí)姆涩,返回"one"
Mockito.when(mockedList.get(0)).thenReturn("one");

// 使用mock對(duì)象 - 會(huì)返回前面設(shè)置好的值"one"挽拂,即便列表實(shí)際上是空的
String str = mockedList.get(0);

Assert.assertTrue("one".equals(str));
Assert.assertTrue(mockedList.size() == 0);

// 驗(yàn)證mock對(duì)象的get方法被調(diào)用過(guò),而且調(diào)用時(shí)傳的參數(shù)是0
Mockito.verify(mockedList).get(0); 

基本分析

讓我們仔細(xì)想想看骨饿,下面這個(gè)代碼:

// 設(shè)置mock對(duì)象的行為 - 當(dāng)調(diào)用其get方法獲取第0個(gè)元素時(shí)亏栈,返回"one"
Mockito.when(mockedList.get(0)).thenReturn("one");

如果按照一般代碼的思路去理解,是要做這么一件事:

  • 調(diào)用mockedList.get方法宏赘,傳入0作為參數(shù)绒北,然后得到其返回值(一個(gè)object),然后再把這個(gè)返回值傳給when方法察署,然后針對(duì)when方法的返回值闷游,調(diào)用thenReturn。好像有點(diǎn)不通贴汪?mockedList.get(0)的結(jié)果脐往,語(yǔ)義上是mockedList的一個(gè)元素,這個(gè)元素傳給when是表示什么意思扳埂?所以业簿,我們不能按照尋常的思路去理解這段代碼。
  • 實(shí)際上這段代碼要做的是描述這么一件事情:當(dāng)mockedList的get方法被調(diào)用阳懂,并且參數(shù)的值是0的時(shí)候梅尤,返回”one”。
  • 很不尋常岩调,對(duì)嗎克饶?如果用平常的面向?qū)ο蟮乃枷雭?lái)設(shè)計(jì)API來(lái)做同樣的事情,估計(jì)結(jié)果是這樣的:
Mockito.returnValueWhen("one", mockedList, "get", 0);

第一個(gè)參數(shù)描述要返回的結(jié)果誊辉,第二個(gè)參數(shù)指定mock對(duì)象矾湃,第三個(gè)參數(shù)指定mock方法,后面的參數(shù)指定mock方法的參數(shù)值堕澄。這樣的代碼邀跃,更符合我們看一般代碼時(shí)候的思路霉咨。

  • 但是,把上面的代碼跟Mockito的代碼進(jìn)行比較拍屑,我們會(huì)發(fā)現(xiàn)途戒,我們的代碼有幾個(gè)問(wèn)題:

    • 不夠直觀
    • 對(duì)重構(gòu)不友好
    • 第二點(diǎn)尤其重要。想象一下僵驰,如果我們要做重構(gòu)喷斋,把get方法改名叫fetch方法,那我們要把”get”字符串替換成”fetch”蒜茴,而字符串替換沒(méi)有編譯器的支持星爪,需要手工去做,或者查找替換粉私,很容易出錯(cuò)顽腾。而Mockito使用的是方法調(diào)用,對(duì)方法的改名诺核,可以用編譯器支持的重構(gòu)來(lái)進(jìn)行抄肖,更加方便可靠。

實(shí)現(xiàn)分析

Mock對(duì)象這件事情窖杀,本質(zhì)上是一個(gè)Proxy模式的應(yīng)用漓摩。

Proxy模式說(shuō)的是,在一個(gè)真實(shí)對(duì)象前面入客,提供一個(gè)proxy對(duì)象幌甘,所有對(duì)真實(shí)對(duì)象的調(diào)用,都先經(jīng)過(guò)proxy對(duì)象痊项,然后由proxy對(duì)象根據(jù)情況锅风,決定相應(yīng)的處理,它可以直接做一個(gè)自己的處理鞍泉,也可以再調(diào)用真實(shí)對(duì)象對(duì)應(yīng)的方法皱埠。Proxy對(duì)象對(duì)調(diào)用者來(lái)說(shuō),可以是透明的咖驮,也可以是不透明的边器。

Java本身提供了構(gòu)建Proxy對(duì)象的API:Java Dynamic Proxy API。Mockito就是用Java提供的Dynamic Proxy API來(lái)實(shí)現(xiàn)的托修。

仔細(xì)分析忘巧,就會(huì)發(fā)現(xiàn),示例代碼最難理解的部分是:

  • 建立Mock對(duì)象(proxy對(duì)象)
  • 配置mock方法(指定其在什么情況下返回什么值)

// 設(shè)置mock對(duì)象的行為 - 當(dāng)調(diào)用其get方法獲取第0個(gè)元素時(shí)睦刃,返回"one"
Mockito.when(mockedList.get(0)).thenReturn("one");

  • 基本思想:
    Mockito使用什么方式來(lái)傳遞信息砚嘴? —— 不是用方法的返回值,而是用某種全局的變量。當(dāng)get方法被調(diào)用的時(shí)候(調(diào)用的實(shí)際上是proxy對(duì)象的get方法)际长,代碼實(shí)際上保存了被調(diào)用的方法名(get)耸采,以及調(diào)用時(shí)候傳遞的參數(shù)(0),然后等到thenReturn方法被調(diào)用的時(shí)候工育,再把”one”保存起來(lái)虾宇,這樣,就有了構(gòu)建一個(gè)stub方法所需的所有信息如绸,就可以構(gòu)建一個(gè)stub方法了嘱朽。
  • 源碼分析:
public Object handle(Invocation invocation) throws Throwable {
     if (invocationContainerImpl.hasAnswersForStubbing()) {
         ...
    }
     ...
     InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
            mockingProgress.getArgumentMatcherStorage(),
           invocation
    );
   mockingProgress.validateState();
    // if verificationMode is not null then someone is doing verify()
   if (verificationMode != null) {
   ...
      }
    // prepare invocation for stubbing   invocationContainerImpl.setInvocationForPotentialStubbing(invocationMatcher);
  OngoingStubbingImpl<T> ongoingStubbing = 
  new OngoingStubbingImpl<T>(invocationContainerImpl);
mockingProgress.reportOngoingStubbing(ongoingStubbing);
   ...
}

注意第1行,第6-9行怔接,可以看到方法調(diào)用的信息(invocation)對(duì)象被用來(lái)構(gòu)造invocationMatcher對(duì)象搪泳,然后在第19-21行,invocationMatcher對(duì)象最終傳遞給了ongoingStubbing對(duì)象蜕提。完成了stub信息的保存森书。

小結(jié)

通過(guò)以上的分析我們可以看到靶端,Mockito在設(shè)計(jì)時(shí)實(shí)際上有意地使用了方法的“副作用”谎势,在返回值之外,還保存了方法調(diào)用的信息杨名,進(jìn)而在最后利用這些信息脏榆,構(gòu)建出一個(gè)mock。而這些信息的保存台谍,是對(duì)Mockito的用戶(hù)完全透明的须喂。

這是一個(gè)經(jīng)典的“反模式”的使用案例〕萌铮“模式”告訴我們坞生,在設(shè)計(jì)方法的時(shí)候,應(yīng)該避免副作用掷伙,一個(gè)方法在被調(diào)用時(shí)候是己,除了return返回值之外,不應(yīng)該產(chǎn)生其他的狀態(tài)改變任柜,尤其不應(yīng)該有“意料之外”的改變卒废。但Mockito完全違反了這個(gè)原則,Mockito的靜態(tài)方法Mockito.anyString(), mockInstance.method(), Mockito.when(), thenReturn()宙地,這些方法摔认,在背后都有很大的“副作用” —— 保存了調(diào)用者的信息,然后利用這些信息去完成任務(wù)宅粥。這就是為什么Mockito的代碼一開(kāi)始會(huì)讓人覺(jué)得奇怪的原因参袱,因?yàn)槲覀兤綍r(shí)不這樣寫(xiě)代碼。

然而,作為一個(gè)Mocking框架蓖柔,這個(gè)“反模式”的應(yīng)用實(shí)際上是一個(gè)好的設(shè)計(jì)辰企。就像我們前面看到的,它帶來(lái)了非常簡(jiǎn)單的API况鸣,以及編譯安全牢贸,可重構(gòu)等優(yōu)良特性。違反直覺(jué)的方法調(diào)用镐捧,在明白其原理和一段時(shí)間的熟悉之后潜索,也顯得非常的自然了。設(shè)計(jì)的原則懂酱,終究是為設(shè)計(jì)目標(biāo)服務(wù)的竹习,原則在總結(jié)出來(lái)之后,不應(yīng)該成為僵硬的教條列牺,根據(jù)需求靈活地應(yīng)用這些原則整陌,才能達(dá)成好的設(shè)計(jì)。在這方面瞎领,Mockito堪稱(chēng)一個(gè)經(jīng)典案例泌辫。

基本用法

verify some behaviour!

 //mock creation
        List mockedList = mock(List.class);

        //using mock object
        mockedList.add("one");
        mockedList.clear();

        //verification
        verify(mockedList).add("one");
        verify(mockedList).clear();

stubbing smth

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

        //Although it is possible to verify a stubbed invocation, usually it's just redundant
        //If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
        //If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here.
        verify(mockedList).get(0);
  • By default, for all methods that return a value, a mock will return either null, a primitive/primitive wrapper value, or an empty collection, as appropriate. For example 0 for an int/Integer and false for a boolean/Boolean.
  • Stubbing can be overridden: for example common stubbing can go to fixture setup but the test methods can override it. Please note that overridding stubbing is a potential code smell that points out too much stubbing
  • Once stubbed, the method will always return a stubbed value, regardless of how many times it is called.
  • Last stubbing is more important - when you stubbed the same method with the same arguments many times. Other words: the order of stubbing matters but it is only meaningful rarely, e.g. when stubbing exactly the same method calls or sometimes when argument matchers are used, etc.

Argument matchers

Mockito verifies argument values in natural java style: by using an equals() method. Sometimes, when extra flexibility is required then you might use argument matchers:

//mock creation
        List mockedList = mock(List.class);
        //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(i -> (Integer)i > 10))).thenReturn(true);

        //following prints "element"
        System.out.println(mockedList.get(999));

        System.out.println(mockedList.add(30));
        System.out.println(mockedList.add(60));

        //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(i -> (Integer)i > 50));

Warning on argument matchers:

If you are using argument matchers, all arguments have to be provided by matchers.
The following example shows verification but the same applies to stubbing:

verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
//above is correct - eq() is also an argument matcher

verify(mock).someMethod(anyInt(), anyString(), "third argument");
//above is incorrect - exception will be thrown because third argument is given without an argument matcher.

Verifying exact number of invocations exact_verification/ at least x/ / never

        //mock creation
        List mockedList = mock(List.class);
        //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) is the default. Therefore using times(1) explicitly can be omitted.

Stubbing void methods with exceptions

   doThrow(new RuntimeException()).when(mockedList).clear();

   //following throws RuntimeException:
   mockedList.clear();

Find redundant invocations

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

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

 //following verification will fail
 verifyNoMoreInteractions(mockedList);

annotation mock

  • Minimizes repetitive mock creation code.
  • Makes the test class more readable.
  • Makes the verification error easier to read because the field name is used to identify the mock.

更多官方詳細(xì)使用示例參考文末Ref 使用

靜態(tài)引用

如果在代碼中靜態(tài)引用了org.mockito.Mockito.*;那就可以直接調(diào)用靜態(tài)方法和靜態(tài)變量而不用創(chuàng)建對(duì)象,譬如直接調(diào)用 mock() 方法九默。
這個(gè)對(duì)于Mockito很好用震放,單是一般我們會(huì)配置包的明確引用,不是*驼修。

除了上面所說(shuō)的使用 mock() 靜態(tài)方法外殿遂,Mockito 還支持通過(guò) @Mock 注解的方式來(lái)創(chuàng)建 mock 對(duì)象。

如果你使用注解乙各,則必須要實(shí)例化 mock 對(duì)象墨礁。

Mockito 在遇到使用注解的字段的時(shí)候,會(huì)調(diào)用MockitoAnnotations.initMocks(this) 來(lái)初始化該 mock 對(duì)象耳峦。另外也可以通過(guò)使用@RunWith(MockitoJUnitRunner.class)來(lái)達(dá)到相同的效果恩静。

import static org.mockito.Mockito.*;

public class MockitoTest  {

        @Mock
        MyDatabase databaseMock; // (1)

        @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); //(2)

        @Test
        public void testQuery()  {
                ClassToTest t  = new ClassToTest(databaseMock); //(3)
                boolean check = t.query("* from t");// (4)
                assertTrue(check); //(5)
                verify(databaseMock).query("* from t"); //(6)
        }
}
// =================說(shuō)明
- 1. 告訴 Mockito 模擬 databaseMock 實(shí)例

- 2. Mockito 通過(guò) @mock 注解創(chuàng)建 mock 對(duì)象

- 3. 使用已經(jīng)創(chuàng)建的mock初始化這個(gè)類(lèi)

- 4. 在測(cè)試環(huán)境下,執(zhí)行測(cè)試類(lèi)中的代碼

- 5. 使用斷言確保調(diào)用的方法返回值為 true

- 6. 驗(yàn)證 query 方法是否被 MyDatabase 的 mock 對(duì)象調(diào)用

配置 mock

當(dāng)我們需要配置某個(gè)方法的返回值的時(shí)候妇萄,Mockito 提供了鏈?zhǔn)降?API 供我們方便的調(diào)用

when(…?.).thenReturn(…?.)可以被用來(lái)定義當(dāng)條件滿(mǎn)足時(shí)函數(shù)的返回值蜕企,如果你需要定義多個(gè)返回值,可以多次定義冠句。當(dāng)你多次調(diào)用函數(shù)的時(shí)候轻掩,Mockito 會(huì)根據(jù)你定義的先后順序來(lái)返回值(stack原理)。Mocks 還可以根據(jù)傳入?yún)?shù)的不同來(lái)定義不同的返回值懦底。譬如說(shuō)你的函數(shù)可以將anyString 或者 anyInt作為輸入?yún)?shù)唇牧,然后定義其特定的放回值罕扎。

@Test
public void test1()  {
        //  創(chuàng)建 mock
        MyClass test = Mockito.mock(MyClass.class);

        // 自定義 getUniqueId() 的返回值
        when(test.getUniqueId()).thenReturn(43);

        // 在測(cè)試中使用mock對(duì)象
        assertEquals(test.getUniqueId(), 43);
}

// 返回多個(gè)值
@Test
public void testMoreThanOneReturnValue()  {
        Iterator i= mock(Iterator.class);
        when(i.next()).thenReturn("Mockito").thenReturn("rocks");
        String result=i.next()+" "+i.next();
        // 斷言
        assertEquals("Mockito rocks", result);
}

// 如何根據(jù)輸入來(lái)返回值
@Test
public void testReturnValueDependentOnMethodParameter()  {
        Comparable c= mock(Comparable.class);
        when(c.compareTo("Mockito")).thenReturn(1);
        when(c.compareTo("Eclipse")).thenReturn(2);
        // 斷言
        assertEquals(1,c.compareTo("Mockito"));
}

// 如何讓返回值不依賴(lài)于輸入
@Test
public void testReturnValueInDependentOnMethodParameter()  {
        Comparable c= mock(Comparable.class);
        when(c.compareTo(anyInt())).thenReturn(-1);
        // 斷言
        assertEquals(-1 ,c.compareTo(9));
}

// 根據(jù)參數(shù)類(lèi)型來(lái)返回值
@Test
public void testReturnValueInDependentOnMethodParameter()  {
        Comparable c= mock(Comparable.class);
        when(c.compareTo(isA(Todo.class))).thenReturn(0);
        // 斷言
        Todo todo = new Todo(5);
        assertEquals(todo ,c.compareTo(new Todo(1)));
}
// 對(duì)于無(wú)返回值的函數(shù),我們可以使用doReturn(…?).when(…?).methodCall來(lái)獲得類(lèi)似的效果丐重。
// 如我們想在調(diào)用某些無(wú)返回值函數(shù)的時(shí)候拋出異常腔召,那么可以使用doThrow 方法
@Test(expected=IOException.class)
public void testForIOException() {
        // 創(chuàng)建并配置 mock 對(duì)象
        OutputStream mockStream = mock(OutputStream.class);
        doThrow(new IOException()).when(mockStream).close();

        // 使用 mock
        OutputStreamWriter streamWriter= new OutputStreamWriter(mockStream);
        streamWriter.close();
}

驗(yàn)證 mock 對(duì)象方法是否被調(diào)用(即所謂的行為驗(yàn)證)

Mockito 會(huì)跟蹤 mock 對(duì)象里面所有的方法和變量。所以我們可以用來(lái)驗(yàn)證函數(shù)在傳入特定參數(shù)的時(shí)候是否被調(diào)用扮惦。這種方式的測(cè)試稱(chēng)行為測(cè)試臀蛛,行為測(cè)試并不會(huì)檢查函數(shù)的返回值,而是檢查在傳入正確參數(shù)時(shí)候函數(shù)是否被調(diào)用崖蜜。

@Test
public void testVerify()  {
        // 創(chuàng)建并配置 mock 對(duì)象
        MyClass test = Mockito.mock(MyClass.class);
        when(test.getUniqueId()).thenReturn(43);

        // 調(diào)用mock對(duì)象里面的方法并傳入?yún)?shù)為12
        test.testing(12);
        test.getUniqueId();
        test.getUniqueId();

        // 查看在傳入?yún)?shù)為12的時(shí)候方法是否被調(diào)用
        verify(test).testing(Matchers.eq(12));

        // 方法是否被調(diào)用兩次浊仆,默認(rèn)是1次
        verify(test, times(2)).getUniqueId();

        // 其他用來(lái)驗(yàn)證函數(shù)是否被調(diào)用的方法
        verify(mock, never()).someMethod("never called");
        verify(mock, atLeastOnce()).someMethod("called at least once");
        verify(mock, atLeast(2)).someMethod("called at least twice");
        verify(mock, times(5)).someMethod("called five times");
        verify(mock, atMost(3)).someMethod("called at most 3 times");
}

使用 Spy 封裝 java 對(duì)象

@Spy或者spy()方法可以被用來(lái)封裝 java 對(duì)象。被封裝后豫领,除非特殊聲明(打樁 stub)抡柿,否則都會(huì)真正的調(diào)用對(duì)象里面的每一個(gè)方法.

// Lets mock a LinkedList
List list = new LinkedList();
List spy = spy(list);

// 可用 doReturn() 來(lái)打樁
doReturn("foo").when(spy).get(0);

// 下面代碼不生效
// 真正的方法會(huì)被調(diào)用
// 將會(huì)拋出 IndexOutOfBoundsException 的異常,因?yàn)?List 為空
when(spy.get(0)).thenReturn("foo");

@InjectMocks 在 Mockito 中進(jìn)行依賴(lài)注入

// 假定我們有 ArticleManager 類(lèi)
public class ArticleManager {
    private User user;
    private ArticleDatabase database;
    ArticleManager(User user) {
     this.user = user;
    }
    void setDatabase(ArticleDatabase database) { }
}


@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest  {

       @Mock ArticleCalculator calculator;
       @Mock ArticleDatabase database;
       @Most User user;

       @Spy private UserProvider userProvider = new ConsumerUserProvider();
        // 這個(gè)類(lèi)會(huì)被 Mockito 構(gòu)造等恐,而類(lèi)的成員方法和變量都會(huì)被 mock 對(duì)象所代替
       @InjectMocks private ArticleManager manager;// (1)

       @Test public void shouldDoSomething() {
               // 假定 ArticleManager 有一個(gè)叫 initialize() 的方法被調(diào)用了
               // 使用 ArticleListener 來(lái)調(diào)用 addListener 方法
               manager.initialize();

               // 驗(yàn)證 addListener 方法被調(diào)用
               verify(database).addListener(any(ArticleListener.class));
       }
}

捕捉參數(shù)Captor

ArgumentCaptor類(lèi)允許我們?cè)趘erification期間訪問(wèn)方法的參數(shù)洲劣。得到方法的參數(shù)后我們可以使用它進(jìn)行測(cè)試。

public class MockitoTests {

        @Rule public MockitoRule rule = MockitoJUnit.rule();

        @Captor
    private ArgumentCaptor> captor;

        @Test
    public final void shouldContainCertainListItem() {
        List asList = Arrays.asList("someElement_test", "someElement");
        final List mockedList = mock(List.class);
        mockedList.addAll(asList);

        verify(mockedList).addAll(captor.capture());
        final List capturedArgument = captor.>getValue();
        assertThat(capturedArgument, hasItem("someElement"));
    }
}

Mockito的限制

而下面三種數(shù)據(jù)類(lèi)型則不能夠被測(cè)試

  • final classes
  • anonymous classes
  • primitive types

實(shí)例:使用 Mockito 創(chuàng)建一個(gè) mock 對(duì)象

// 創(chuàng)建一個(gè)Twitter API 的例子
public interface ITweet {
        String getMessage();
}

public class TwitterClient {
        public void sendTweet(ITweet tweet) {
                String message = tweet.getMessage();
                // send the message to Twitter
        }
}

// 模擬 ITweet 的實(shí)例
@Test
public void testSendingTweet() {
        TwitterClient twitterClient = new TwitterClient();

        ITweet iTweet = mock(ITweet.class);

        when(iTweet.getMessage()).thenReturn("Using mockito is great");

        twitterClient.sendTweet(iTweet);
}

// 驗(yàn)證方法調(diào)用
@Test
public void testSendingTweet() {
        TwitterClient twitterClient = new TwitterClient();

        ITweet iTweet = mock(ITweet.class);

        when(iTweet.getMessage()).thenReturn("Using mockito is great");

        twitterClient.sendTweet(iTweet);
        // 驗(yàn)證 getMessage() 方法至少調(diào)用一次课蔬。
        verify(iTweet, atLeastOnce()).getMessage();
}

模擬靜態(tài)方法

因?yàn)?Mockito 不能夠 mock 靜態(tài)方法囱稽,因此我們可以使用 Powermock。

// 模擬了 NetworkReader 的依賴(lài)
@RunWith( PowerMockRunner.class )
@PrepareForTest( NetworkReader.class )
public class MyTest {

final class NetworkReader {
    public static String getLocalHostname() {
        String hostname = "";
        try {
            InetAddress addr = InetAddress.getLocalHost();
            // Get hostname
            hostname = addr.getHostName();
        } catch ( UnknownHostException e ) {
        }
        return hostname;
    }
}

// 測(cè)試代碼

 @Test
public void testSomething() {
    mockStatic( NetworkUtil.class );
    when( NetworkReader.getLocalHostname() ).andReturn( "localhost" );
}

// 有時(shí)候我們可以在靜態(tài)方法周?chē)庆o態(tài)的方法來(lái)達(dá)到和 Powermock 同樣的效果购笆。
class FooWraper { 
      void someMethod() { 
           Foo.someStaticMethod() 
       } 
}

Ref:
官網(wǎng):http://site.mockito.org/
介紹:http://www.infoq.com/cn/articles/mockito-design/
使用:
https://juejin.im/entry/578f11aec4c971005e0caf82
http://static.javadoc.io/org.mockito/mockito-core/2.19.0/org/mockito/Mockito.html
git:https://github.com/mockito/mockito
其他:
https://waylau.com/mockito-quick-start/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末粗悯,一起剝皮案震驚了整個(gè)濱河市虚循,隨后出現(xiàn)的幾起案子同欠,更是在濱河造成了極大的恐慌,老刑警劉巖横缔,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铺遂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡茎刚,警方通過(guò)查閱死者的電腦和手機(jī)襟锐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)膛锭,“玉大人粮坞,你說(shuō)我怎么就攤上這事〕跽” “怎么了莫杈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)奢入。 經(jīng)常有香客問(wèn)我筝闹,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任关顷,我火速辦了婚禮糊秆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘议双。我一直安慰自己痘番,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布平痰。 她就那樣靜靜地躺著夫偶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪觉增。 梳的紋絲不亂的頭發(fā)上兵拢,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音逾礁,去河邊找鬼说铃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嘹履,可吹牛的內(nèi)容都是我干的腻扇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼砾嫉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼幼苛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起焕刮,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舶沿,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后配并,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體括荡,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年溉旋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了畸冲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡观腊,死狀恐怖邑闲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梧油,我是刑警寧澤苫耸,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站婶溯,受9級(jí)特大地震影響鲸阔,放射性物質(zhì)發(fā)生泄漏偷霉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一褐筛、第九天 我趴在偏房一處隱蔽的房頂上張望类少。 院中可真熱鬧,春花似錦渔扎、人聲如沸硫狞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)残吩。三九已至,卻和暖如春倘核,著一層夾襖步出監(jiān)牢的瞬間泣侮,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工紧唱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留活尊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓漏益,卻偏偏與公主長(zhǎng)得像蛹锰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绰疤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容