1. 程式人生 > >使用Powermock和mockito來進行單元測試

使用Powermock和mockito來進行單元測試

簡介

Mockito是一個流行的Mocking框架。它使用起來簡單,學習成本很低,而且具

有非常簡潔的API,測試程式碼的可讀性很高。因此它十分受歡迎,使用者群越來越

多,很多的開源的軟體也選擇了Mockito。

要想了解更多有關Mockito的資訊,請訪問它的官方網站:http://mockito.org/

Stub 和Mock

在開始使用Mockito之前,先簡單的瞭解一下Stub和Mock的區別。

Stub物件用來提供測試時所需要的測試資料,可以對各種互動設定相應的迴應。

例如我們可以設定方法呼叫的返回值等等。Mockito中when(…).thenReturn(…)

這樣的語法便是設定方法呼叫的返回值。另外也可以設定方法在何時呼叫會拋異

常等。

Mock物件用來驗證測試中所依賴物件間的互動是否能夠達到預期。Mockito中用

verify(…).methodXxx(…) 語法來驗證 methodXxx 方法是否按照預期進行了調

用。

有關stub和mock的詳細論述見,Martin Fowler文章《Mocks Aren't Stub》

http://martinfowler.com/articles/mocksArentStubs.html

在 Mocking 框架中所謂的mock 物件實際上是作為上述的stub 和mock 物件同時

使用的。因為它既可以設定方法呼叫返回值,又可以驗證方法的呼叫。

Mockito 的獲取

Jar 包的獲取

可以訪問下面的連結來下載最新的Jar包,筆者使用的當前最新版為:1.8.5

http://code.google.com/p/mockito/downloads/list

Maven

如果專案是通過Maven管理的,需要在專案的Pom.xml中增加如下的依賴:

<dependencies>

<dependency>

<groupId>org.mockito</groupId>

<artifactId>mockito-all</artifactId>

<version>1.8.5</version>

<scope>test</scope>

</dependency>

</dependencies>

從一個例項開始

Mocktio包的引入

在程式中可以import org.mockito.Mockito;然後呼叫它的static方法,或者

import static org.mockito.Mockito.*;個人傾向於後者,因為這樣可以更方

便些。

一個簡單的例子

import static org.junit.Assert.*;

import static org.mockito.Mockito.*;

import java.util.Iterator;

import org.junit.Test;

/**

*

* @author Brian Zhao

*/

public class SimpleTest {

@Test

public void simpleTest(){

//arrange

Iterator i=mock(Iterator.class);

when(i.next()).thenReturn("Hello").thenReturn("World");

//act

String result=i.next()+" "+i.next();

//verify

verify(i, times(2)).next();

//assert

assertEquals("Hello World", result);

}

}

在上面的例子中包含了Mockito的基本功能:

建立 Mock 物件

建立Mock物件的語法為,mock(class or interface)。例子中建立了Iterator

介面的mock物件。

設定方法呼叫的預期返回

通過when(mock.someMethod()).thenReturn(value) 來設定mock物件某個方

法呼叫時的返回值。例子中我們對Iterator介面的next()方法呼叫進行了預期

設定,當呼叫next()方法時會返回”Hello”,由於連續設定了返回值,因此當第

二次呼叫時將返回”World”。

驗證方法呼叫

接下來對mock物件的next()方法進行了一系列實際的呼叫。mock物件一旦建

立便會自動記錄自己的互動行為,所以我們可以有選擇的對它的互動行為進行驗

證。在Mockito中驗證mock物件互動行為的方法是

verify(mock).someMethod(…)。於是用此方法驗證了next()方法呼叫,因為調

用了兩次,所以在verify中我們指定了times引數(times的具體應用在後面

會繼續介紹)。最後assert返回值是否和預期一樣。

Mock物件的建立和Stubbing

Mock 物件的建立

mock(Class<T> classToMock)

mock(Class<T> classToMock, String name)

可以對類和介面進行mock 物件的建立,建立的時候可以為mock 物件命名,也

可以忽略命名引數。為mock 物件命名的好處就是除錯的時候會很方便,比如,

我們mock 多個物件,在測試失敗的資訊中會把有問題的mock 物件打印出來,

有了名字我們可以很容易定位和辨認出是哪個mock物件出現的問題。另外它也

有限制,對於final類、匿名類和Java的基本型別是無法進行mock的。

Mock 物件的期望行為及返回值設定

我們已經瞭解到可以通過when(mock.someMethod()).thenReturn(value) 來

設定mock物件的某個方法呼叫時的返回值,但它也同樣有限制對於static和final

修飾的方法是無法進行設定的。下面來詳細的介紹一下有關方法及返回值的設定:

首先假設我們建立Iterator介面的mock物件

Iterator<String> i = mock(Iterator.class);

對方法設定返回值

when(i.next()).thenReturn("Hello")

對方法設定返回異常

when(i.next()).thenThrow(new RuntimeException())

Mockito支援迭代風格的返回值設定

第一種方式

when(i.next()).thenReturn("Hello").thenReturn("World")

第二種方式

when(i.next()).thenReturn("Hello", "World")

上面的設定相當於:

when(i.next()).thenReturn("Hello")

when(i.next()).thenReturn("World")

第一次呼叫i.next()將返回”Hello”,第二次的呼叫會返回”World”。

Stubbing的另一種語法

doReturn(Object) 設定返回值

doReturn("Hello").when(i).next();

迭代風格

doReturn("Hello").doReturn("World").when(i).next();

返回值的次序為從左至右,第一次呼叫返回”Hello”,第二次返回”World”。

doThrow(Throwable) 設定返回異常

doThrow(new RuntimeException()).when(i).next();

因為這種語法的可讀性不如前者,所以能使用前者的情況下儘量使用前者,當然

在後面要介紹的Spy除外。

對 void 方法進行方法預期設定

void方法的模擬不支援when(mock.someMethod()).thenReturn(value)這樣的

語法,只支援下面的方式:

doNothing() 模擬不做任何返回(mock物件void方法的預設返回)

doNothing().when(i).remove();

doThrow(Throwable) 模擬返回異常

doThrow(new RuntimeException()).when(i).remove();

迭代風格

doNothing().doThrow(new RuntimeException()).when(i).remove();

第一次呼叫remove方法什麼都不做,第二次呼叫丟擲RuntimeException異常。

Argument Matcher(引數匹配器)

Mockito通過equals()方法,來對方法引數進行驗證。但有時我們需要更加靈活的

引數需求,比如,匹配任何的String型別的引數等等。引數匹配器就是一個能夠

滿足這些需求的工具。

Mockito框架中的Matchers 類內建了很多引數匹配器,而我們常用的Mockito對

象便是繼承自Matchers。這些內建的引數匹配器如,anyInt()匹配任何int型別參

數,anyString()匹配任何字串,anySet()匹配任何Set 等。下面通過例子來說明

如何使用內建的引數匹配器:

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

}

Stubbing時使用內建引數匹配器

例子中,首先mock 了List 介面,然後用迭代的方式模擬了get 方法的返回值,

這裡用了anyInt()引數匹配器來匹配任何的int 型別的引數。所以當第一次呼叫

get方法時輸入任意引數為100方法返回”Hello”,第二次呼叫時輸入任意引數200

返回值”World”。

Verfiy時使用引數匹配器

最後進行verfiy 驗證的時候也可將引數指定為anyInt()匹配器,那麼它將不關心

呼叫時輸入的引數的具體引數值。

注意事項

如果使用了引數匹配器,那麼所有的引數需要由匹配器來提供,否則將會報錯。

假如我們使用引數匹配器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)。

詳細的內建引數匹配器請參考:

http://docs.mockito.googlecode.com/hg/org/mockito/Matchers.html

Mock物件的行為驗證

之前介紹瞭如何設定mock物件預期呼叫的方法及返回值。下面介紹方法呼叫的

驗證,而它關注點則在mock 物件的互動行為上,比如驗證mock 物件的某個方

法呼叫引數,呼叫次數,順序等等。下面來看例子:

@Test

public void verifyTestTest() {

List<String> mock = mock(List.class);

List<String> mock2 = mock(List.class);

when(mock.get(0)).thenReturn("hello");

mock.get(0);

mock.get(1);

mock.get(2);

mock2.get(0);

verify(mock).get(2);

verify(mock, never()).get(3);

verifyNoMoreInteractions(mock);

verifyZeroInteractions(mock2);

}

驗證的基本方法

我們已經熟悉了使用verify(mock).someMethod(…)來驗證方法的呼叫。例子中,

我們mock 了List 介面,然後呼叫了mock 物件的一些方法。驗證是否呼叫了

mock.get(2)方法可以通過verify(mock).get(2)來進行。verify 方法的呼叫不

關心是否模擬了get(2)方法的返回值,只關心mock 物件後,是否執行了

mock.get(2),如果沒有執行,測試方法將不會通過。

驗證未曾執行的方法

在verify方法中可以傳入never()方法引數來確認mock.get(3)方法不曾被執行過。

另外還有很多呼叫次數相關的引數將會在下面提到。

查詢多餘的方法呼叫

verifyNoMoreInteractions()方法可以傳入多個mock物件作為引數,用來驗證傳入

的這些mock 物件是否存在沒有驗證過的呼叫方法。本例中傳入引數mock,測

試將不會通過,因為我們只verify了mock物件的get(2)方法,沒有對get(0)和get(1)

進行驗證。為了增加測試的可維護性,官方不推薦我們過於頻繁的在每個測試方

法中都使用它,因為它只是測試的一個工具,只在你認為有必要的時候才用。

查詢沒有互動的mock物件

verifyZeroInteractions()也是一個測試工具,原始碼和verifyNoMoreInteractions()的實

現是一樣的,為了提高邏輯的可讀性,所以只不過名字不同。在例子中,它的目

的是用來確認mock2物件沒有進行任何互動,但mock2執行了get(0)方法,所以

這裡測試會報錯。由於它和verifyNoMoreInteractions()方法實現的原始碼都一樣,

因此如果在verifyZeroInteractions(mock2)執行之前對mock.get(0)進行了

驗證那麼測試將會通過。

對 Mock物件方法的呼叫次數、順序和超時進行驗證

驗證方法呼叫的次數

如果要驗證Mock 物件的某個方法呼叫次數,則需給verify 方法傳入相關的驗證

引數,它的呼叫介面是verify(T mock, VerificationMode mode) 。如:

verify(mock,times(3)).someMethod(argument) 驗證mock 物件

someMethod(argument)方法是否呼叫了三次。times(N)引數便是驗證呼叫次數的

引數,N 代表方法呼叫次數。其實verify 方法中如果不傳呼叫次數的驗證引數,

它預設傳入的便是times(1),即驗證mock 物件的方法是否只被呼叫一次,如果

有多次呼叫測試方法將會失敗。

Mockito除了提供times(N)方法供我們呼叫外,還提供了很多可選的方法:

never() 沒有被呼叫,相當於times(0)

atLeast(N) 至少被呼叫N次

atLeastOnce() 相當於atLeast(1)

atMost(N) 最多被呼叫N次

超時驗證

Mockito 提供對超時的驗證,但是目前不支援在下面提到的順序驗證中使用。進

行超時驗證和上述的次數驗證一樣,也要在verify 中進行引數的傳入,引數為

timeout(int millis),timeout方法中輸入的是毫秒值。下面看例子:

驗證someMethod()是否能在指定的100毫秒中執行完畢

verify(mock, timeout(100)).someMethod();

結果和上面的例子一樣,在超時驗證的同時可進行呼叫次數驗證,預設次數為1

verify(mock, timeout(100).times(1)).someMethod();

在給定的時間內完成執行次數

verify(mock, timeout(100).times(2)).someMethod();

給定的時間內至少執行兩次

verify(mock, timeout(100).atLeast(2)).someMethod();

另外timeout也支援自定義的驗證模式,

verify(mock, new Timeout(100,

yourOwnVerificationMode)).someMethod();

驗證方法呼叫的順序

Mockito 同樣支援對不同Mock 物件不同方法的呼叫次序進行驗證。進行次序驗

證是,我們需要建立InOrder物件來進行支援。例:

建立 mock物件

List<String> firstMock = mock(List.class);

List<String> secondMock = mock(List.class);

呼叫mock物件方法

firstMock.add("was called first");

firstMock.add("was called first");

secondMock.add("was called second");

secondMock.add("was called third");

建立InOrder 物件

inOrder方法可以傳入多個mock物件作為引數,這樣便可對這些mock物件的方

法進行呼叫順序的驗證InOrder inOrder = inOrder( secondMock,

firstMock );

驗證方法呼叫

接下來我們要呼叫InOrder物件的verify方法對mock方法的呼叫順序進行驗證。

注意,這裡必須是你對呼叫順序的預期。

InOrder物件的verify方法也支援呼叫次數驗證,上例中,我們期望

firstMock.add("was called first")方法先執行並執行兩次,所以進行了下

面的驗證inOrder.verify(firstMock,times(2)).add("was called first")。

其次執行了secondMock.add("was called second")方法,繼續驗證此方法的

執行inOrder.verify(secondMock).add("was called second")。如果mock

方法的呼叫順序和InOrder中verify的順序不同,那麼測試將執行失敗。

InOrder的verifyNoMoreInteractions()方法

它用於確認上一個順序驗證方法之後,mock 物件是否還有多餘的互動。它和

Mockito提供的靜態方法verifyNoMoreInteractions 不同,InOrder的驗證是基於順

序的,另外它只驗證建立它時所提供的mock 物件,在本例中只對firstMock 和

secondMock有效。例如:

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

inOrder.verifyNoMoreInteractions();

在驗證secondMock.add("was called second")方法之後,加上InOrder的

verifyNoMoreInteractions方法,表示此方法呼叫後再沒有多餘的互動。例子

中會報錯,因為在此方法之後還執行了secondMock.add("was called third")。

現在將上例改成:

inOrder.verify(secondMock).add("was called third");

inOrder.verifyNoMoreInteractions();

測試會恢復為正常,因為在secondMock.add("was called third")之後已經沒

有多餘的方法呼叫了。如果這裡換成Mockito類的verifyNoMoreInteractions方法測

試還是會報錯,它查詢的是mock物件中是否存在沒有驗證的呼叫方法,和順序

是無關的。

Mock物件的重置

Mockito提供了reset(mock1,mock2……)方法,用來重置mock物件。當mock物件

被重置後,它將回到剛建立完的狀態,沒有任何stubbing和方法呼叫。這個特性

平時是很少用到的,因為我們大都為每個test 方法建立mock,所以沒有必要對

它進行重置。官方提供這個特性的唯一目的是使得我們能在有容器注入的mock

物件中工作更為方便。所以,當決定要使用這個方法的時候,首先應該考慮一下

我們的測試程式碼是否簡潔和專注,測試方法是否已經超長了。

Answer介面(方法預期回撥介面)的應用

Answer介面說明

對mock物件的方法進行呼叫預期的設定,可以通過thenReturn()來指定返回值,

thenThrow()指定返回時所拋異常,通常來說這兩個方法足以應對一般的需求。但

有時我們需要自定義方法執行的返回結果,Answer 介面就是滿足這樣的需求而

存在的。另外,建立mock 物件的時候所呼叫的方法也可以傳入Answer 的例項

mock(java.lang.Class<T> classToMock, Answer defaultAnswer),它可以用來處理那

些mock物件沒有stubbing的方法的返回值。

InvocationOnMock 物件的方法

Answer 介面定義了引數為InvocationOnMock 物件的answer 方法,利用

InvocationOnMock提供的方法可以獲取mock 方法的呼叫資訊。下面是它提供的

方法:

getArguments() 呼叫後會以Object陣列的方式返回mock方法呼叫的引數。

getMethod() 返回java.lang.reflect.Method 物件

getMock() 返回mock物件

callRealMethod() 真實方法呼叫,如果mock的是介面它將會丟擲異常

通過一個例子來看一下Answer 的使用。我們自定義CustomAnswer 類,它實現

了Answer介面,返回值為String型別。

public class CustomAnswer implements Answer<String> {

public String answer(InvocationOnMock invocation) throws

Throwable {

Object[] args = invocation.getArguments();

Integer num = (Integer)args[0];

if( num>3 ){

return "yes";

} else {

throw new RuntimeException();

}

}

}

這個返回值是這樣的邏輯,如果呼叫mock某個方法輸入的引數大於3返回”yes”,

否則丟擲異常。

Answer介面的使用

應用方式如下:

首先對List介面進行mock

List<String> mock = mock(List.class);

指定方法的返回處理類CustomAnswer,因為引數為4大於3所以返回字串”yes”

when(mock.get(4)).thenAnswer(new CustomAnswer());

另外一種方式

doAnswer(new CustomAnswer()).when(mock.get(4));

對void方__________法也可以指定Answer來進行返回處理,如:

doAnswer(new xxxAnswer()).when(mock).clear();

當設定了Answer後,指定方法的呼叫結果就由我們定義的Answer介面來處理了。

另外我們也可以使用匿名內部類來進行應用:

@Test

public void customAnswerTest(){

List<String> mock = mock(List.class);

when(mock.get(4)).thenAnswer(new Answer(){

public String answer(InvocationOnMock invocation) throws

Throwable {

Object[] args = invocation.getArguments();

Integer num = (Integer)args[0];

if( num>3 ){

return "yes";

} else {

throw new RuntimeException();

}

}

});

System.out.println(mock.get(4));

}

自定義引數匹配器

Mockito引數匹配器的實現使用了Hamcrest框架(一個書寫匹配器物件時允許直

接定義匹配規則的框架,網址:http://code.google.com/p/hamcrest/)。它已經提供了

許多規則供我們使用, Mockito在此基礎上也內建了很規則。但有時我們還是需

要更靈活的匹配,所以需要自定義引數匹配器。

ArgumentMatcher 抽象類

自定義引數匹配器的時候需要繼承ArgumentMatcher抽象類,它實現了Hamcrest

框架的Matcher介面,定義了describeTo方法,所以我們只需要實現matches 方

法在其中定義規則即可。

下面自定義的引數匹配器是匹配size大小為2 的List:

class IsListOfTwoElements extends ArgumentMatcher<List> {

public boolean matches(Object list) {

return ((List) list).size() == 2;

}

}

@Test

public void argumentMatchersTest(){

List mock = mock(List.class);

when(mock.addAll(argThat(new

IsListOfTwoElements()))).thenReturn(true);

mock.addAll(Arrays.asList("one", "two", "three"));

verify(mock).addAll(argThat(new IsListOfTwoElements()));

}

argThat(Matcher<T> matcher)方法用來應用自定義的規則,可以傳入任何實現

Matcher 介面的實現類。上例中在stubbing 和verify addAll 方法時通過

argThat(Matcher<T> matcher) , 傳入了自定義的引數匹配器

IsListOfTwoElements 用來匹配size 大小為2 的List。因為例子中傳入List

的元素為三個,所以測試將失敗。

較複雜的引數匹配將會降低測試程式碼的可讀性。有時實現引數物件的equals()

方法是個不錯的選擇(Mockito預設使用equals()方法進行引數匹配),它可以

使測試程式碼更為整潔。另外,有些場景使用引數捕獲器(ArgumentCaptor)要比

自定義引數匹配器更加合適。

利用ArgumentCaptor(引數捕獲器)捕獲方法引數進行驗證

在某些場景中,不光要對方法的返回值和呼叫進行驗證,同時需要驗證一系列交

互後所傳入方法的引數。那麼我們可以用引數捕獲器來捕獲傳入方法的引數進行

驗證,看它是否符合我們的要求。

ArgumentCaptor 介紹

通過 ArgumentCaptor 物件的forClass(Class<T> clazz)方法來構建ArgumentCaptor

物件。然後便可在驗證時對方法的引數進行捕獲,最後驗證捕獲的引數值。如果

方法有多個引數都要捕獲驗證,那就需要建立多個ArgumentCaptor物件處理。

ArgumentCaptor的Api

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

}

首先構建ArgumentCaptor需要傳入捕獲引數的物件,例子中是String。接著要在

verify 方法的引數中呼叫argument.capture()方法來捕獲輸入的引數,之後

argument變數中就儲存了引數值,可以用argument.getValue()獲取。當某個

物件進行了多次呼叫後,如mock2 物件,這時呼叫argument.getValue()獲取

到的是最後一次呼叫的引數。如果要獲取所有的引數值可以呼叫

argument.getAllValues(),它將返回引數值的List。

在某種程度上引數捕獲器和引數匹配器有很大的相關性。它們都用來確保傳入

mock 物件引數的正確性。然而,當自定義的引數匹配器的重用性較差時,用參

數捕獲器會更合適,只需在最後對引數進行驗證即可。

Spy-物件的監視

Mock 物件只能呼叫stubbed 方法,調用不了它真實的方法。但Mockito 可以監

視一個真實的物件,這時對它進行方法呼叫時它將呼叫真實的方法,同時也可以

stubbing 這個物件的方法讓它返回我們的期望值。另外不論是否是真實的方法調

用都可以進行verify驗證。和建立mock物件一樣,對於final類、匿名類和Java

的基本型別是無法進行spy的。

監視物件

監視一個物件需要呼叫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)方法。

下面是官方文件給出的例子:

@Test

public void spyTest2() {

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

}

RETURNS_SMART_NULLS 和RETURNS_DEEP_STUBS

RETURNS_SMART_NULLS

RETURNS_SMART_NULLS是實現了Answer介面的物件,它是建立mock物件時的

一個可選引數,mock(Class, Answer)。在建立mock物件時,有的方法我們沒有進

行stubbing,所以在呼叫的時候有時會返回Null這樣在進行處理時就很可能丟擲

NullPointerException。如果通過RETURNS_SMART_NULLS引數來建立的mock物件

在呼叫沒有stubbed的方法時他將返回SmartNull。例如:返回型別是String 它將

返回空字串””;是int,它將返回0;如果是List,它會返回一個空的List。另

外,在堆疊中可以看到SmartNull的友好提示。

@Test

public void returnsSmartNullsTest() {

List mock = mock(List.class, RETURNS_SMART_NULLS);

System.out.println(mock.get(0));

System.out.println(mock.toArray().length);

}

由於使用了RETURNS_SMART_NULLS 引數來建立mock 物件,所以在執行下面的

操作時將不會丟擲NullPointerException 異常,另外堆疊也提示了相關的資訊

“SmartNull returned by unstubbed get() method on mock”。

RETURNS_DEEP_STUBS

同上面的引數一樣RETURNS_DEEP_STUBS也是一個建立mock物件時的備選引數。

例如我們有Account 物件和RailwayTicket 物件,RailwayTicket 是Account 的一個

屬性。

public class Account {

private RailwayTicket railwayTicket;

public RailwayTicket getRailwayTicket() {

return railwayTicket;

}

public void setRailwayTicket(RailwayTicket railwayTicket) {

this.railwayTicket = railwayTicket;

}

}

public class RailwayTicket {

private String destination;

public String getDestination() {

return destination;

}

public void setDestination(String destination) {

this.destination = destination;

}

}

下面通過RETURNS_DEEP_STUBS來建立mock 物件。

@Test

public void deepstubsTest(){

Account account = mock(Account.class, RETURNS_DEEP_STUBS);

when(account.getRailwayTicket().getDestination()).thenReturn("

Beijing");

account.getRailwayTicket().getDestination();

verify(account.getRailwayTicket()).getDestination();

assertEquals("Beijing",

account.getRailwayTicket().getDestination());

}

上例中,我們只建立了Account 的mock 物件,沒有對RailwayTicket 建立mock,

因為通過RETURNS_DEEP_STUBS引數程式會自動進行mock所需要的物件,所以

上面的例子等價於:

@Test

public void deepstubsTest2(){

Account account = mock(Account.class);

RailwayTicket railwayTicket = mock(RailwayTicket.class);

when(account.getRailwayTicket()).thenReturn(railwayTicket);

when(railwayTicket.getDestination()).thenReturn("Beijing");

account.getRailwayTicket().getDestination();

verify(account.getRailwayTicket()).getDestination();

assertEquals("Beijing",

account.getRailwayTicket().getDestination());

}

為了程式碼整潔和確保它的可讀性,我們應該少用這個特性。

Mockito對Annotation的支援

Mockito 支援對變數進行註解,例如將mock 物件設為測試類的屬性,然後通過

註解的方式@Mock 來定義它,這樣有利於減少重複程式碼,增強可讀性,易於排

查錯誤等。除了支援@Mock,Mockito支援的註解還有@Spy(監視真實的物件),

@Captor(引數捕獲器),@InjectMocks(mock物件自動注入)。

Annotation的初始化

只有Annotation還不夠,要讓它們工作起來還需要進行初始化工作。初始化的方

法為:MockitoAnnotations.initMocks(testClass)引數testClass是你所寫

的測試類。一般情況下在Junit4的@Before 定義的方法中執行初始化工作,如

下:

@Before

public void initMocks() {

MockitoAnnotations.initMocks(this);

}

除了上述的初始化的方法外,還可以使用Mockito 提供的Junit Runner:

MockitoJUnitRunner這樣就省略了上面的步驟。

@RunWith(MockitoJUnit44Runner.class)

public class ExampleTest {

...

}

@Mock 註解

使用@Mock註解來定義mock物件有如下的優點:

1. 方便mock物件的建立

2. 減少mock物件建立的重複程式碼

3. 提高測試程式碼可讀性

4. 變數名字作為mock物件的標示,所以易於排錯

@Mock註解也支援自定義name 和answer屬性。下面是官方給出的@Mock使用

的例子:

public class ArticleManagerTest extends SampleBaseTestCase {

@Mock

private ArticleCalculator calculator;

@Mock(name = "dbMock")

private ArticleDatabase database;

@Mock(answer = RETURNS_MOCKS)

private UserProvider userProvider;

private ArticleManager manager;

@Before

public void setup() {

manager = new ArticleManager(userProvider, database,

calculator);

}

}

public class SampleBaseTestCase {

@Before

public void initMocks() {

MockitoAnnotations.initMocks(this);

}

}

@Spy 註解

Spy的使用方法請參閱前面的章節,在此不再贅述,下面是使用方法:

public class Test{

@Spy

Foo spyOnFoo = new Foo();

@Before

public void init(){

MockitoAnnotations.initMocks(this);

}

...

}

@Captor 註解

@Captor是引數捕獲器的註解,有關用法見前章,通過註解的方式也可以更便捷

的對它進行定義。使用例子如下:

public class Test {

@Captor

ArgumentCaptor<AsyncCallback<Foo>> captor;

@Before

public void init() {

MockitoAnnotations.initMocks(this);

}

@Test

public void shouldDoSomethingUseful() {

// ...

verify(mock.doStuff(captor.capture()));

assertEquals("foo", captor.getValue());

}

}

@InjectMocks 註解

通過這個註解,可實現自動注入mock 物件。當前版本只支援setter 的方式進行

注入,Mockito 首先嚐試型別注入,如果有多個型別相同的mock 物件,那麼它

會根據名稱進行注入。當注入失敗的時候Mockito不會丟擲任何異常,所以你可

能需要手動去驗證它的安全性。

例:

@RunWith(MockitoJUnit44Runner.class)

public class ArticleManagerTest {

@Mock

private ArticleCalculator calculator;

@Mock

private ArticleDatabase database;

@Spy

private UserProvider userProvider = new ConsumerUserProvider();

@InjectMocks

private ArticleManager manager = new ArticleManager();

@Test

public void shouldDoSomething() {

manager.initiateArticle();

verify(database).addListener(any(ArticleListener.class));

}

}

上例中, ArticleDatabase 是ArticleManager 的一個屬性, 由於

ArticleManager 是註解@InjectMocks 標註的,所以會根據型別自動呼叫它的

setter方法為它設定ArticleDatabase。

首先了解mockito 才能知道powermock,看名字就知道powermock更強,下面來介紹

<dependency>
 <groupId>org.powermock</groupId>
 <artifactId>powermock-api-mockito</artifactId>
 <version>1.4.10</version>
 <scope>test</scope>
</dependency>

<dependency>
 <groupId>org.powermock</groupId>
 <artifactId>powermock-module-junit4</artifactId>
 <version>1.4.10</version>
 <scope>test</scope>
</dependency>

下面我將以Power Mock的mockito的版本來講述如何使用Power Mock。
測試目標類:

public class ClassUnderTest {

    public boolean callArgumentInstance(File file) {
        return file.exists();
    }
    
    public boolean callInternalInstance(String path) {
        File file = new File(path);
        return file.exists();
    }
    
    public boolean callFinalMethod(ClassDependency refer) {
        return refer.isAlive();
    }
    
    public boolean callSystemFinalMethod(String str) {
        return str.isEmpty();
    }
    
    public boolean callStaticMethod() {
        return ClassDependency.isExist();
    }
    
    public String callSystemStaticMethod(String str) {
        return System.getProperty(str);
    }
    
    public boolean callPrivateMethod() {
        return isExist();
    }
    
    private boolean isExist() {
        // do something
        return false;
    }
}


依賴類:

public class ClassDependency {

    public static boolean isExist() {
        // do something
        return false;
    }
    
    public final boolean isAlive() {
        // do something
        return false;
    }
}



接下來,對6個測試用例進行逐個的講解。
首先需要使用@RunWith(PowerMockRunner.class)將測試用例的runner改為PowerMockRunner
1、testCallArgumentInstance:Mock引數傳遞的物件

    @Test
    public void testCallArgumentInstance() {
        File file = PowerMockito.mock(File.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(file.exists()).thenReturn(true);
        Assert.assertTrue(underTest.callArgumentInstance(file));
    }


需要mock的物件是由引數傳進去的,這是最普通的一種mock方式,jMock,EasyMock,Mockito都能實現。
步驟:
a、通過PowerMockito.mock(File.class)創建出一個mock物件
b、然後再通過PowerMockito.when(file.exists()).thenReturn(false);來指定這個mock物件具體的行為
c、再將mock物件作為引數傳遞個測試方法,執行測試方法。

2、testCallInternalInstance:Mock方法內部new出來的物件

    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallInternalInstance() throws Exception {
        File file = PowerMockito.mock(File.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.whenNew(File.class).withArguments("bbb").thenReturn(file);
        PowerMockito.when(file.exists()).thenReturn(true);
        Assert.assertTrue(underTest.callInternalInstance("bbb"));
    }


需要mock的物件是在方法內部new出來的,這是一種比較常見的mock方式。
步驟(已經講過的步驟省略):
a、通過PowerMockito.whenNew(File.class).withArguments("bbb").thenReturn(file)來指定當以引數為bbb建立File物件的時候,返回已經mock的File物件。
b、在測試方法之上加註解@PrepareForTest(ClassUnderTest.class),註解裡寫的類是需要mock的new物件程式碼所在的類。

3、testCallFinalMethod:Mock普通物件的final方法。

    @Test
    @PrepareForTest(ClassDependency.class)
    public void testCallFinalMethod() {
        
        ClassDependency depencency = PowerMockito.mock(ClassDependency.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(depencency.isAlive()).thenReturn(true);
        Assert.assertTrue(underTest.callFinalMethod(depencency));
    }


Mock的步驟和之前的一樣,只是需要在測試方法之上加註解@PrepareForTest(ClassDependency.class),註解裡寫的類是需要mock的final方法所在的類。

4、testCallStaticMethod:Mock靜態方法。

    @Test
    @PrepareForTest(ClassDependency.class)
    public void testCallStaticMethod() {
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.mockStatic(ClassDependency.class);
        PowerMockito.when(ClassDependency.isExist()).thenReturn(true);
        Assert.assertTrue(underTest.callStaticMethod());
    }


步驟:
a、通過PowerMockito.mockStatic(ClassDependency.class);表示需要mock這個類裡的靜態方法
b、在測試方法之上加註解@PrepareForTest(ClassDependency.class),註解裡寫的類是需要mock的靜態方法所在的類。

5、testCallSystemStaticMethod:Mock JDK中類的靜態方法。
   testCallSystemFinalMethod:Mock JDK物件的final方法。

 

  @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallSystemStaticMethod() {
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.getProperty("aaa")).thenReturn("bbb");
        Assert.assertEquals("bbb", underTest.callJDKStaticMethod("aaa"));
    }

    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallSystemFinalMethod() {
        
        String str = PowerMockito.mock(String.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(str.isEmpty()).thenReturn(false);
        Assert.assertFalse(underTest.callJDKFinalMethod(str));
    }


和Mock普通物件的靜態方法、final方法一樣,只不過註解裡寫的類不一樣@PrepareForTest(ClassUnderTest.class),註解裡寫的類是需要呼叫系統方法所在的類。

6、testCallPrivateMethod:Mock私有方法。

    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallPrivateMethod() throws Exception {
        ClassUnderTest underTest = PowerMockito.mock(ClassUnderTest.class);
        PowerMockito.when(underTest.callPrivateMethod()).thenCallRealMethod();
        PowerMockito.when(underTest, "isExist").thenReturn(true);
        Assert.assertTrue(underTest.callPrivateMethod());
    }


和Mock普通方法一樣,只是需要加註解@PrepareForTest(ClassUnderTest.class),註解裡寫的類是私有方法所在的類。

完整的測試用例類:

@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {

    @Test
    public void testCallArgumentInstance() {
        File file = PowerMockito.mock(File.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(file.exists()).thenReturn(true);
        Assert.assertTrue(underTest.callArgumentInstance(file));
    }
    
    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallInternalInstance() throws Exception {
        File file = PowerMockito.mock(File.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.whenNew(File.class).withArguments("bbb").thenReturn(file);
        PowerMockito.when(file.exists()).thenReturn(true);
        Assert.assertTrue(underTest.callInternalInstance("bbb"));
    }
    
    @Test
    @PrepareForTest(ClassDependency.class)
    public void testCallFinalMethod() {
        
        ClassDependency depencency = PowerMockito.mock(ClassDependency.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(depencency.isAlive()).thenReturn(true);
        Assert.assertTrue(underTest.callFinalMethod(depencency));
    }
    
    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallSystemFinalMethod() {
        
        String str = PowerMockito.mock(String.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(str.isEmpty()).thenReturn(false);
        Assert.assertFalse(underTest.callSystemFinalMethod(str));
    }
    
    @Test
    @PrepareForTest(ClassDependency.class)
    public void testCallStaticMethod() {
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.mockStatic(ClassDependency.class);
        PowerMockito.when(ClassDependency.isExist()).thenReturn(true);
        Assert.assertTrue(underTest.callStaticMethod());
    }
    
    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallSystemStaticMethod() {
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.getProperty("aaa")).thenReturn("bbb");
        Assert.assertEquals("bbb", underTest.callSystemStaticMethod("aaa"));
    }
    
    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallPrivateMethod() throws Exception {
        ClassUnderTest underTest = PowerMockito.mock(ClassUnderTest.class);
        PowerMockito.when(underTest.callPrivateMethod()).thenCallRealMethod();
        PowerMockito.when(underTest, "isExist").thenReturn(true);
        Assert.assertTrue(underTest.callPrivateMethod());
    }
}

------------------------------------------------------------

下面是我自己的例子

mock靜態類

private void prepareForGosServiceFactory()
 {
  ClassPathXmlApplicationContext ctx=PowerMockito.mock(ClassPathXmlApplicationContext.class);
  
  try {
   PowerMockito.whenNew(ClassPathXmlApplicationContext.class).withAnyArguments().thenReturn(ctx);
   UpdateSoService updateSoService=PowerMockito.mock(UpdateSoService.class);
   PowerMockito.when(ctx.getBean("wrapUpdateOrderHessianCall")).thenReturn(updateSoService);
   PowerMockito.mockStatic(GosServiceFactory.class);
   PowerMockito.when(GosServiceFactory.getUpdateOrderService()).thenReturn(updateSoService);
   PowerMockito.when(updateSoService.updateRemarkIdByOrderId(Mockito.any(RemarkInput.class)));
  } catch (Exception e) {
  }
 }
@Test
 public void testDigExperience() {
  long peid = 1234L;
  int siteId = 1;
  String ip = "127.0.0.1";
  org.mockito.Mockito.when(productExperienceDao.countExperienceDig(peid, ip)).thenReturn(0L);// mock 返回值
  org.mockito.Mockito.doNothing().when(productExperienceDao).updateExperienceUpNum(peid,siteId); // mock void方法
  org.mockito.Mockito.doNothing().when(productExperienceDao).updateExperienceDownNum(peid, siteId);
  ProductExperienceDig ped = new ProductExperienceDig();
  org.mockito.Mockito.when(productExperienceDao.insertPED(ped)).thenReturn(0L);
  target.digExperience_pe(peid, ip, 1l, "up");
  target.digExperience_pe(peid, ip, 1l, "down");
 }

//一個方法重複呼叫多次,返回值不同的情況,可以這樣寫
  org.mockito.Mockito.when(productExperienceDao.getProductExperienceByIdAndProductIdAndSiteType(Mockito.anyLong(),Mockito.anyLong(),Mockito.anyInt()))
  .thenReturn(experienceOld,experienceOld1,experienceOld2,experienceOld3,experienceOld4);

//注意,如果引數有一個使用了Any型別,那麼全部都必須用Any型別
  org.mockito.Mockito.when(productExperienceDao.queryBoutqueAndTopMostPEByMainProdIds(Mockito.anyList(),Mockito.anyInt(), Mockito.anyInt())).thenReturn(yhdMap);