轉載:http://blog.csdn.net/u013428664/article/details/44095889
簡介
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);