什么是 Mock
mock 的中文譯為: 仿制的,模擬的,虛假的。對(duì)于測(cè)試框架來(lái)說(shuō),即構(gòu)造出一個(gè)模擬/虛假的對(duì)象,使我們的測(cè)試能順利進(jìn)行下去。
為什么要使用 Mock 測(cè)試框架
單元測(cè)試是為了驗(yàn)證我們的代碼運(yùn)行正確性,我們注重的是代碼的流程以及結(jié)果的正確與否。而對(duì)比于真實(shí)運(yùn)行代碼,可能其中有一些外部依賴的構(gòu)建步驟相對(duì)麻煩,如果我們還是按照真實(shí)代碼的構(gòu)建規(guī)則構(gòu)造出外部依賴,會(huì)大大增加單元測(cè)試的工作,代碼也會(huì)參雜太多非測(cè)試部分的內(nèi)容,測(cè)試用例顯得復(fù)雜難懂,而采用 Mock 框架,我們可以虛擬出一個(gè)外部依賴,只注重代碼的流程與結(jié)果,真正地實(shí)現(xiàn)測(cè)試目的。
Mock 測(cè)試框架好處
- 可以很簡(jiǎn)單的虛擬出一個(gè)復(fù)雜對(duì)象(比如虛擬出一個(gè)接口的實(shí)現(xiàn)類)
- 可以配置 mock 對(duì)象的行為
- 可以使測(cè)試用例只注重測(cè)試流程與結(jié)果
- 減少外部類或系統(tǒng)帶來(lái)的副作用
······
Mockito 簡(jiǎn)介
Most popular Mocking framework for unit tests written in Java
Mockito 是當(dāng)前最流行的單元測(cè)試 mock 框架。
使用
在 Module 的 build.gradle中添加如下內(nèi)容:
dependencies {
//Mockito for unit tests
testImplementation "org.mockito:mockito-core:2.+"
//Mockito for Android tests
androidTestImplementation 'org.mockito:mockito-android:2.+'
}
這里稍微解釋下:
mockito-core用于本地單元測(cè)試,其測(cè)試代碼路徑位于:module-name/src/test/java/
mockiot-android用于儀器測(cè)試,即需要運(yùn)行android設(shè)備進(jìn)行測(cè)試,其測(cè)試代碼路徑位于: module-name/src/androidTest/java/
更多詳細(xì)信息,請(qǐng)查看官網(wǎng):測(cè)試應(yīng)用
ps:
mockito-core最新版本可以在 Maven 中查詢:mockito-core
mockito-android最新版本可以在 Maven 中查詢:mockito-android
示例
- 普通單元測(cè)試使用 mockito(
mockito-core),路徑:module-name/src/test/java/
這里摘用官網(wǎng)的Demo:
now you can verify interactions -- 檢驗(yàn)調(diào)對(duì)象相關(guān)行為是否被調(diào)用
import static org.mockito.Mockito.*;
// mock creation
List mockedList = mock(List.class);
// using mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one"); //調(diào)用了add("one")行為
mockedList.clear();//調(diào)用了clear()行為
// selective, explicit, highly readable verification
verify(mockedList).add("one");//檢驗(yàn)add("one")是否已被調(diào)用
verify(mockedList).clear();//檢驗(yàn)clear()是否已被調(diào)用
這里 mock 了一個(gè) List(這里只是為了用作 Demo 示例,通常對(duì)于 List 這種簡(jiǎn)單的類對(duì)象創(chuàng)建而言,直接 new 一個(gè)真實(shí)的對(duì)象即可,無(wú)需進(jìn)行 mock),verify 會(huì)檢驗(yàn)對(duì)象是否在前面已經(jīng)執(zhí)行了相關(guān)行為,這里mockedList在verify之前已經(jīng)執(zhí)行了add("one")和clear()行為,所以verify會(huì)通過(guò)。
stub method calls -- 配置/方法行為
// you can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
// stubbing appears before the actual execution
when(mockedList.get(0)).thenReturn("first");
// the following prints "first"
System.out.println(mockedList.get(0));
// the following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
這里對(duì)幾個(gè)比較重要的點(diǎn)進(jìn)行解析:
-
when(mockedList.get(0)).thenReturn("first")
這句話 Mockito 會(huì)解析為:當(dāng)對(duì)象mockedList調(diào)用方法方法get并且參數(shù)為0時(shí),返回結(jié)果為"first",這相當(dāng)于定制了我們 mock 對(duì)象的行為結(jié)果(mockLinkedList對(duì)象為mockedList,指定其行為get(0)返回結(jié)果為"first")。 -
mockedList.get(999)
由于mockedList沒(méi)有指定get(999)的行為,所以其結(jié)果為null。因?yàn)?Mockito 的底層原理是使用 cglib 動(dòng)態(tài)生成一個(gè)代理類對(duì)象,因此,mock 出來(lái)的對(duì)象其實(shí)質(zhì)就是一個(gè)代理,該代理在沒(méi)有配置/指定行為的情況下,默認(rèn)返回空值:
- Android單元測(cè)試使用 mockito(
mockito-android),路徑:module-name/src/androidTest/java/,該測(cè)試需運(yùn)行安卓真機(jī)/模擬器。
上面的 Demo 使用的是靜態(tài)方法mock模擬出一個(gè)實(shí)例,我們還可以通過(guò)注解@Mock也模擬出一個(gè)實(shí)例:
@Mock
private Intent mIntent;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void mockAndroid(){
Intent intent = mockIntent();
assertThat(intent.getAction()).isEqualTo("com.yn.test.mockito");
assertThat(intent.getStringExtra("Name")).isEqualTo("Whyn");
}
private Intent mockIntent(){
when(mIntent.getAction()).thenReturn("com.yn.test.mockito");
when(mIntent.getStringExtra("Name")).thenReturn("Whyn");
return mIntent;
}
對(duì)于 @Mock, @Spy, @InjectMocks等注解的成員變量的初始化到目前為止有2種方法:
- 對(duì) JUnit 測(cè)試類添加
@RunWith(MockitoJUnitRunner.class) - 在
@Before方法內(nèi)調(diào)用初始化方法:MockitoAnnotations.initMocks(Object)
現(xiàn)在,正如我們上面的測(cè)試用例所示,對(duì)于@Mock等注解的成員變量的初始化又多了一種方法:MockitoRule
規(guī)則MockitoRule會(huì)自動(dòng)幫我們調(diào)用MockitoAnnotations.initMocks(this)去實(shí)例化出注解的成員變量,我們就無(wú)需手動(dòng)進(jìn)行初始化了。
ps:上面要注意的一個(gè)點(diǎn)就是,斷言使用的是開(kāi)源庫(kù):Truth
更多Demo,請(qǐng)查看:Mockito
Mockito 一些重要方法簡(jiǎn)介
-
@Mock/mock:實(shí)例化虛擬對(duì)象
//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);
對(duì)于所有方法,
mock對(duì)象默認(rèn)返回null,原始類型/原始類型包裝類默認(rèn)值,或者空集合。比如對(duì)于int/Integer類型,則返回0,對(duì)于boolean/Bollean則返回false。行為配置(
stub)是可以被復(fù)寫的:比如通常的對(duì)象行為是具有一定的配置,但是測(cè)試方法可以復(fù)寫這個(gè)行為。請(qǐng)謹(jǐn)記行為復(fù)寫可能表明潛在的行為太多了。一旦配置了行為,方法總是會(huì)返回配置值,無(wú)論該方法被調(diào)用了多少次。
最后一次行為配置是更加重要的 - 當(dāng)你為一個(gè)帶有相同參數(shù)的相同方法配置了很多次,最后一次起作用。
-
Argument matchers - 參數(shù)匹配
Mockito 通過(guò)參數(shù)對(duì)象的equals()方法來(lái)驗(yàn)證參數(shù)是否一致,當(dāng)需要更多的靈活性時(shí),可以使用參數(shù)匹配器:
//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));
參數(shù)匹配器允許更加靈活的驗(yàn)證和行為配置。更多內(nèi)置匹配器和自定義參數(shù)匹配器例子請(qǐng)參考:ArgumentMatchers,MockitoHamcrest
注意:如果使用了參數(shù)匹配器,那么所有的參數(shù)都需要提供一個(gè)參數(shù)匹配器:
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.
類似anyObject(),eq()這類匹配器并不返回匹配數(shù)值。他們內(nèi)部記錄一個(gè)匹配器堆棧并返回一個(gè)空值(通常為null)。這個(gè)實(shí)現(xiàn)是為了匹配 java 編譯器的靜態(tài)類型安全,這樣做的后果就是你不能在檢驗(yàn)/配置方法外使用anyObject(),eq()等方法。
- Verifying exact number of invocations / at least x / never - 校驗(yàn)次數(shù)
//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");
校驗(yàn)次數(shù)方法常用的有如下幾個(gè):
| Method | Meaning |
|---|---|
| times(n) | 次數(shù)為n,默認(rèn)為1(times(1)) |
| never() | 次數(shù)為0,相當(dāng)于times(0)
|
| atLeast(n) | 最少n次 |
| atLeastOnce | 最少一次 |
| atMost(n) | 最多n次 |
4. 配置返回類型為void的方法拋出異常:doThrow
doThrow(new RuntimeException()).when(mockedList).clear();
//following throws RuntimeException:
mockedList.clear();
5. Verification in order - 按順序校驗(yàn)
有時(shí)對(duì)于一些行為,有先后順序之分,所以,當(dāng)我們?cè)谛r?yàn)時(shí),就需要考慮這個(gè)行為的先后順序:
// 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
-
Stubbing consecutive calls (iterator-style stubbing) - 存根連續(xù)調(diào)用
對(duì)于同一個(gè)方法,如果我們想讓其在多次調(diào)用中分別返回不同的數(shù)值,那么就可以使用存根連續(xù)調(diào)用:
when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
//First call: throws runtime exception:
mock.someMethod("some arg");
//Second call: prints "foo"
System.out.println(mock.someMethod("some arg"));
//Any consecutive call: prints "foo" as well (last stubbing wins).
System.out.println(mock.someMethod("some arg"));
也可以使用下面更簡(jiǎn)潔的存根連續(xù)調(diào)用方法:
when(mock.someMethod("some arg"))
.thenReturn("one", "two", "three");
注意:存根連續(xù)調(diào)用要求必須使用鏈?zhǔn)秸{(diào)用,如果使用的是同個(gè)方法的多個(gè)存根配置,那么只有最后一個(gè)起作用(覆蓋前面的存根配置)。
//All mock.someMethod("some arg") calls will return "two"
when(mock.someMethod("some arg"))
.thenReturn("one")
when(mock.someMethod("some arg"))
.thenReturn("two")
-
doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() family of methods
對(duì)于返回類型為void的方法,存根要求使用另一種形式的when(Object)函數(shù),因?yàn)榫幾g器要求括號(hào)內(nèi)不能存在void方法。
例如,存根一個(gè)返回類型為void的方法,要求調(diào)用時(shí)拋出一個(gè)異常:
doThrow(new RuntimeException()).when(mockedList).clear();
//following throws RuntimeException:
mockedList.clear();
-
Spying on real objects - 監(jiān)視真實(shí)對(duì)象
我們前面使用的都是 mock 出來(lái)一個(gè)對(duì)象,這樣,當(dāng)我們沒(méi)有配置/存根其具體行為的話,結(jié)果就會(huì)返回空類型。而使用特務(wù)對(duì)象(spy),那么對(duì)于我們沒(méi)有存根的行為,它會(huì)調(diào)用原來(lái)對(duì)象的方法??梢园?code>spy想象成 局部mock。
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");
注意:由于spy是 局部mock,所以有時(shí)候使用when(Object)時(shí),無(wú)法做到存根作用,此時(shí),就可以考慮使用doReturn|Answer|Throw()這類方法進(jìn)行存根:
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);
spy并不是對(duì)真實(shí)對(duì)象的代理,相反的,它對(duì)傳遞過(guò)來(lái)的真實(shí)對(duì)象進(jìn)行復(fù)制,所以,對(duì)于任何真實(shí)對(duì)象的操作,spy對(duì)象并不會(huì)感知到,同理,對(duì)spy對(duì)象的任何操作,也不會(huì)影響到真實(shí)對(duì)象。
當(dāng)然,如果你想使用mock對(duì)象進(jìn)行 局部mock,通過(guò)doCallRealMethod|thenCallRealMethod方法也是可以的:
//you can enable partial mock capabilities selectively on mocks:
Foo mock = mock(Foo.class);
//Be sure the real implementation is 'safe'.
//If real implementation throws exceptions or depends on specific state of the object then you're in trouble.
when(mock.someMethod()).thenCallRealMethod();
-
Aliases for behavior driven development (Since 1.8.0) - 測(cè)試驅(qū)動(dòng)開(kāi)發(fā)
以行為驅(qū)動(dòng)開(kāi)發(fā)格式使用 //given //when //then 注釋為測(cè)試用法基石編寫測(cè)試用例,這正是 Mockito 官方編寫測(cè)試用例方法,強(qiáng)烈建議使用這種方式進(jìn)行測(cè)試編寫。
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());
}
- Custom verification failure message (Since 2.1.0) - 自定義錯(cuò)誤校驗(yàn)輸出信息
// 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();
11.@InjectMock -- 構(gòu)造器,方法,成員變量依賴注入
使用@InjectMock注解時(shí),Mockito 會(huì)為類構(gòu)造器,方法或者成員變量依據(jù)它們的類型進(jìn)行自動(dòng)mock
public class InjectMockTest {
@Mock
private User user;
@Mock
private ArticleDatabase database;
@InjectMocks
private ArticleManager manager;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void testInjectMock() {
// calls addListener with an instance of ArticleListener
manager.initialize();
// validate that addListener was called
verify(database).addListener(any(ArticleListener.class));
}
public static class ArticleManager {
private User user;
private ArticleDatabase database;
public ArticleManager(User user, ArticleDatabase database) {
super();
this.user = user;
this.database = database;
}
public void initialize() {
database.addListener(new ArticleListener());
}
}
public static class User {
}
public static class ArticleListener {
}
public static class ArticleDatabase {
public void addListener(ArticleListener listener) {
}
}
}
成員變量manager類型為ArticleManager,其上注解了@InjectMocks,所以要mock出manager,Mockito 會(huì)自動(dòng)mock出ArticleManager所需的構(gòu)造參數(shù)(即user和database),最終mock得到一個(gè)ArticleManager,賦值給manager。
-
ArgumentCaptor-- 參數(shù)捕捉
ArgumentCaptor允許我們?cè)?code>verify的時(shí)候獲取方法參數(shù)內(nèi)容,這使得我們能在測(cè)試過(guò)程中能對(duì)調(diào)用方法參數(shù)進(jìn)行捕捉并測(cè)試。
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Captor
private ArgumentCaptor<List<String>> captor;
@Test
public void testArgumentCaptor(){
List<String> asList = Arrays.asList("someElement_test", "someElement");
final List<String> mockedList = mock(List.class);
mockedList.addAll(asList);
verify(mockedList).addAll(captor.capture());//when verify,you can capture the arguments of the calling method
final List<String> capturedArgument = captor.getValue();
assertThat(capturedArgument, hasItem("someElement"));
}
Mocktio 限制
- 不能
mock靜態(tài)方法 - 不能
mock構(gòu)造器 - 不能
mock方法equals(),hashCode()
更多限制點(diǎn),請(qǐng)查看:FAQ
PowerMockito
針對(duì) Mocktio 無(wú)法mock靜態(tài)方法等限制,使用 PowerMockito 則可以解決這一限制。
- PowerMockito使用方法
- Dowanload:
testImplementation "org.mockito:mockito-core:2.8.47"
testImplementation 'org.powermock:powermock-api-mockito2:1.7.1'
testImplementation 'org.powermock:powermock-module-junit4:1.7.1'
詳情請(qǐng)查看:Mockito 2 Maven
注: 上面只所以不使用最新的 Mockito 版本,是因?yàn)楦鶕?jù) PowerMockito 官方文檔,目前 PowerMockito 版本對(duì)應(yīng)支持的 Mockito 版本如下圖所示:

因此,這里就選擇 Mockito 2.8.47(2.8.x最新版本)
- 示例
//static method
public class Static {
public static String firstStaticMethod() {
return "I am a firstStatic method";
}
public static String secondStaticMethod() {
return "I am a secondStatic method";
}
}
//