Mockito官網java
Mockito是一個mock框架。可以幫助咱們使用更加簡潔的API,寫更漂亮、可讀性更強的測試代碼。android
mock怎麼理解?「模擬」。git
就拿mvp架構來講。當你想要測試presenter的某個方法時,若是在該方法裏,有調用到model層的方法。 而你要根據model層這個方法的返回值,或者產生的反作用,來檢測presenter裏面的代碼。github
這時候,Mockito就能派上用場了。你能夠mock這個model的方法,或者返回任意你想要的值,或者讓這個方法什麼也不作(對無返回值的方法而言),甚至讓這個方法拋各類異常,幫助你檢測在各類不一樣的狀況下,你所寫的方法,是否都能按你設計的步驟順利執行。數組
testImplementation "org.mockito:mockito-core:2.27.0"
複製代碼
mock(Class<T>
)、spy(Class<T>
)、spy(Object)均會生成一個mock實例。bash
只有mock實例,才能調用Mockito的API來mock各類方法。否則會拋各類異常。架構
注意,文中的「mock實例」,除非特別強調。通常都泛指經由mock(Class)、spy(Class)或spy(Object)方法產生的實例。記筆記~app
使用方法以下:MockitoSample mockSample = mock(MockitoSample.class);
MockitoSample spySample = spy(MockitoSample.class);
MockitoSample spyRealSample = spy(new MockitoSample());
複製代碼
上述操做中,mock(Class<T>
)、spy(Class<T>
)一般能夠簡化爲下面的形式。但你要mock多個類的時候,較爲簡便:框架
@Mock
private MockitoSample mockSample;
@Spy
private MockitoSample spySample;
@Before
public void setup(){
MockitoAnnotations.initMocks(this);
}
複製代碼
<T>
)和spy(Object)其中,由下面的部分Mockito源碼可知,spy(Class<T>
)、spy(Object)沒有本質的區別。(因此,下文在介紹mock、spy區別的時候,爲簡便起見。只說spy(Class<T>
)。)ide
public static <T> T spy(Class<T> classToSpy) {
return MOCKITO_CORE.mock(classToSpy, withSettings()
.useConstructor()//該方法返回一個MockSettings類的實例
.defaultAnswer(CALLS_REAL_METHODS));
}
public static <T> T spy(T object) {
return MOCKITO_CORE.mock((Class<T>) object.getClass(), withSettings()
.spiedInstance(object)//該方法也是返回一個MockSettings類的實例
.defaultAnswer(CALLS_REAL_METHODS));
}
複製代碼
mock(Class<T>
)、spy(Class<T>
)產生的實例,都能調用Mockito的API來mock方法。
經由mock(Class<T>
)產生的實例,若是沒有mock一個方法,就嘗試經過該實例調用該方法,不會執行方法的真實邏輯,即不會執行任何操做。若是該方法有返回值,則Object類型、String類型、數組默認返回null,基本類型的數值類型默認返回0、boolean類型默認返回false,集合默認返回空集合,非null。
但經由spy(Class<T>
)產生的實例,若是沒有mock一個方法,就嘗試經過該實例調用該方法,會執行方法真實邏輯。若是該方法有返回值,則返回真實邏輯執行後產生的值。
驗證代碼以下:
/**
* publicMethodNoReturnThrowException是一個無返回值、會拋空指針異常的public方法。爲了方便
* 理解,文中給出的測試方法,都儘可能遵循這種命名。固然,項目實際運用不會這樣命名。
*/
@Test
public void mockClass_notMockPublicMethodNoReturnThrowException(){
mockSample.publicMethodNoReturnThrowException();
}
@Test(expected = NullPointerException.class)
public void spyClass_notMockPublicMethodNoReturnThrowException(){
spySample.publicMethodNoReturnThrowException();
}
複製代碼
對於上述知識點的更多驗證代碼,請參閱文末給出的測試demo。
Mockito提供了不少相似when...thenReturn...
或者doReturn...when...
的方法,都是常見的mock一個方法的手段。
不少時候,這二者是能夠互相通用的,你能夠選擇使用when...thenReturn...
,也能夠選擇使用doReturn...when...
。如:
String expected = "mockPublicMethodReturnString";
//注意,這裏並無真的調用publicMethodReturnString()方法。該代碼的含義是當mockSample調用
//publicMethodReturnString()方法時,將會返回你指望的值expected。
when(mockSample.publicMethodReturnString()).thenReturn(expected);
//跟前者等價
doReturn(expected).when(mockSample).publicMethodReturnString();
複製代碼
但when...thenReturn...
和doReturn...when...
仍是有很多區別的:
1)when...thenReturn...
更適合咱們的閱讀習慣。而doReturn...when...
有點反人類。
2)when...thenReturn...
在mock一個方法時,能進行編譯期類型檢查。而doReturn...when...
不行。但這並非多重要的特性,由於單元測試運行速度快,doReturn...when...
一運行也能立馬檢測出來錯誤。
//傳入錯誤的返回值類型int,編譯器將會報錯
when(mockSample.publicMethodReturnString()).thenReturn(1);
//傳入錯誤的返回值類型int,編譯器不會報錯,只有在運行時才能檢測到錯誤
doReturn(1).when(mockSample).publicMethodReturnString();
複製代碼
3)when...thenReturn...
沒法mock返回值爲void的方法。而doReturn...when...
能夠。
//publicMethodNoReturnThrowException是一個返回值爲void,會拋空指針異常的方法。
//when...thenReturn...沒有對應的方法。也不能mock返回值爲void的方法。
doNothing().when(mockSample).publicMethodNoReturnThrowException();
//when...thenReturn...有對應的方法。但也不能mock返回值爲void的方法。
doThrow(IllegalArgumentException.class).when(mockSample).publicMethodNoReturnThrowException();
複製代碼
4)mock、spy產生的mock實例,使用這二者mock方法時,有時會產生不一樣的行爲:
經由mock(Class<T>
)產生的實例,經過when...thenReturn...
來mock一個方法。而後用該mock實例調用該方法時,在返回指定值以前,不會走真實邏輯。
經由mock(Class<T>
)產生的實例,經過doReturn...when...
來mock一個方法。而後用該mock實例調用該方法時,在返回指定值以前,不會走真實邏輯。
經由spy(Class<T>
)產生的實例,經過when...thenReturn...
來mock一個方法。而後用該mock實例調用該方法時,在返回指定值以前,會走真實邏輯。(這裏使用時,須要特別注意。由於這一點,在使用spy(Class<T>
)產生的實例來mock方法的時候,我的不推薦使用when...thenReturn...
,最好使用doReturn...when...
)
經由spy(Class<T>
)產生的實例,經過doReturn...when...
來mock一個方法。而後用該mock實例調用該方法時,在返回指定值以前,不會走真實邏輯。
驗證代碼,請參閱文末給出的測試demo。
參考資料:Mockito - difference between doReturn() and when()
主要用於驗證一個方法被調用過多少次。使用示例代碼:
@Test
public void verify_publicMethodReturnString() {
verify(mockSample, never()).publicMethodReturnString();
mockSample.publicMethodReturnString();
//默認狀況下是times(1)。times(1)能夠被省略。
verify(mockSample).publicMethodReturnString();
mockSample.publicMethodReturnString();
verify(mockSample, times(2)).publicMethodReturnString();
}
複製代碼
主要用於配合verify方法,驗證方法的調用。使用示例代碼:
@Test
public void verify_publicMethodCalculate() {
//能夠不使用參數匹配器。
verify(mockSample, never()).publicMethodCalculate(1, 2);
//若是你使用參數匹配器(isA(Class<T>)、anyXxx()、eq()),全部的參數都必須由匹配器提供。。
verify(mockSample, never()).publicMethodCalculate(isA(int.class), isA(int.class));
verify(mockSample, never()).publicMethodCalculate(anyInt(), anyInt());
verify(mockSample, never()).publicMethodCalculate(eq(1), eq(2));
mockSample.publicMethodCalculate(1, 2);
verify(mockSample).publicMethodCalculate(1, 2);
verify(mockSample).publicMethodCalculate(isA(int.class), isA(int.class));
verify(mockSample).publicMethodCalculate(anyInt(), anyInt());
verify(mockSample).publicMethodCalculate(eq(1), eq(2));
verify(mockSample, never()).publicMethodCalculate(1, 1);
verify(mockSample, never()).publicMethodCalculate(eq(1), eq(1));
}
複製代碼
看前面看得一臉懵逼?不要緊,來實戰一下吧~
下面演示如何測試常見的mvp代碼presenter裏面的一個loadData()方法。
該Presenter:
public class MainPresenter implements MainContract.Presenter {
private MainContract.View view;
private TestDataSource testDataSource;
public MainPresenter(TestDataSource testDataSource) {
this.testDataSource = testDataSource;
}
@Override
public void attachView(MainContract.View view) {
this.view = view;
}
@Override
public void loadData() {
getDataAndHandle();
}
private void getDataAndHandle() {
//省略一大堆的處理邏輯......
testDataSource.getData()(new TestDataSource.GetDataCallback() {
@Override
public void onSuccess(List<Person> peoples) {
//省略一大堆的處理邏輯......
if (view != null) view.showPersons(peoples);
}
@Override
public void onError() {
//省略一大堆的處理邏輯......
if (view != null) view.showNotDataToast();
}
});
}
}
複製代碼
注意:
爲了便於咱們測試,presenter的設計也很講究。這裏使用到了依賴注入的思想。model層的TestDataSource的實例,是在presenter的構造方法調用以前就建立好,再傳進presenter裏面的。這就是一個最簡單的依賴注入實現。而Dagger2這些依賴注入框架,只是簡化咱們手動一個個去new要注入的實例的繁瑣步驟。
爲何要使用依賴注入?當咱們要測試loadData()方法時,咱們要使用Mockito控制testDataSource的getData(TestDataSource.GetDataCallback)方法,按照咱們的意願返回。但前面也說了,「只有mock實例,才能調用Mockito的API來mock各類方法」。若是你的TestDataSource實例是在presenter的構造方法裏面建立的。那麼你怎麼用你的mock實例替換它?誠然,你能夠暴露一對set/get方法,用來替換原來代碼中的testDataSource,但該set/get方法僅是爲了測試而妥協,並無別的實際用處。若是你有多個要mock的類,那豈不是要多寫一堆set/get方法?
但若是你使用依賴注入,就能夠避免這種尷尬。若是咱們一開始,傳入presenter的就是一個mock實例,那麼一切迎刃而解。
還有,須要注意的是,若是你使用了Dagger2,在寫測試代碼時,不建議使用Dagger2建立這個Presenter。直接像下面代碼同樣,new一個Presenter就行了。
presenter代碼:
private void getDataAndHandle() {
testRepository.getData(new GetDataCallback() {
@Override
public void onSuccess(List<Person> peoples) {
if (view != null) view.showPersons(peoples);
}
@Override
public void onError() {
if (view != null) view.showNotDataToast();
}
});
}
複製代碼
測試代碼:
public class MainPresenterTest {
@Mock
private TestDataSource testDataSource;
@Mock
private MainContract.View view;
private MainPresenter mainPresenter;
/**
* {@link ArgumentCaptor}Captor是一個功能強大的Mockito API,用於捕獲參數值並使用它們,
* 對它們進行進一步的行動或斷言。但我在本身的項目裏,相比回調,更多的時候,用的都是
* RxJava來獲取model層的數據。好處是,不用聲明各類回調接口,並且RxJava在設計的時候,就考
* 慮到了測試的問題,更易於寫測試代碼。
*/
@Captor
private ArgumentCaptor<TestDataSource.GetDataCallback> getDataCallbackCaptor;
private List<Person> peoples;
@Before
public void setup() {
//快速mock多個類
MockitoAnnotations.initMocks(this);
//注意,傳入的是一個已經經由mock(Class`<T>`)產生的實例
mainPresenter = new MainPresenter(testDataSource);
mainPresenter.attachView(view);
peoples = Arrays.asList(new Person("Tony"), new Person("Alice"));
}
@Test
public void loadData() {
mainPresenter.loadData();
verify(testDataSource, times(1)).getData(getDataCallbackCaptor.capture());
//驗證獲取數據成功後的邏輯是否順利完成
getDataCallbackCaptor.getValue().onSuccess(peoples);
verify(view, times(1)).showPersons(peoples);
//驗證獲取數據失敗後的邏輯是否順利完成
getDataCallbackCaptor.getValue().onError();
verify(view, times(1)).showNotDataToast();
}
}
複製代碼
更多的測試Presenter的示例代碼,能夠參考谷歌的android-architecture幾個分支裏面的測試代碼,好比:
1)todo-mvp:presenter層使用回調獲取model層的數據。
2)todo-mvp-rxjava:presenter層使用rxjava獲取model層的數據兩個分支。
mock、spy在實際運用時,該作何選擇?
簡單歸納爲:
這時使用mock(Class<T>
)。好比,測試presenter,咱們要mock掉model層的方法,通常都是使用mock(Class<T>
)。但有人會說,若是我只想mock掉model層的部分方法,一些方法仍是讓它走真實邏輯呢?通常來講,不會這樣子作,畢竟咱們如今要測試的是presenter的方法,應該排除model的干擾,mock掉model層的方法。
這時使用spy(Class<T>
)。好比,現實項目中,我有一個Printer類,大體代碼以下(固然,項目裏的代碼比這還複雜得多):
public void print(String filePath, Callback callback) {
if (getFormat(filePath).equals("pdf")) {
String newFilePath = transform(filePath);
jumpToPrinterShare(newFilePath, callback);
} else {
jumpToPrinterShare(filePath, callback);
}
}
複製代碼
當我想測試print方法在傳入不一樣類型的文件時,可否順利跳轉到PrinterShare,遇到了點小問題。 因爲打印機軟件PrinterShare對含中文字符的pdf的渲染很差,因此要用第三方框架,把pdf轉成圖片再打印,也就是這個transform方法。因爲它過於複雜,又涉及到第三方庫,可能會影響到咱們的測試,因此須要mock該方法。這時,就須要使用spy。注意,這裏使用spy(Class<T>
)時,傳入的是Printer這個類。而後使用mock實例,調用Mockito的相應API來mock掉transform方法。最後用mock實例,直接調用print方法。這樣print方法會走真實邏輯,但若是執行到調用transform方法的地方,不會真的執行此方法,而是直接用你mock的值,繼續往下執行剩餘邏輯。
測試代碼:
public class PrinterTest {
@Spy
private Printer printer;
@Mock
Callback callback;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testPrinterPDFSuccess() {
doReturn("D:\\demo\\a.jpg").when(printer).transform(anyString());
printer.print("D:\\demo\\a.pdf", callback);
verify(callback).onSucess("jpg");
}
}
複製代碼
Mockito主要用於單元測試上。使用時,也須要注意一下代碼的設計結構,方便測試。
另外,Mockito是不能mock私有方法、靜態方法的。2.1.0版本之前的Mockito是不能mock final類和final方法的,以後的也要經過配置一些相關文件才行(Mock the unmockable: opt-in mocking of final classes/methods)。所以,它的補充框架PowerMock也應運而生。(有時候,2.1.0之後的Mockito,採用上述配置文件也未必能mock final類和final方法,跟你的java版本有關)
文中的相關測試例子,以及更多的測試例子都可以在UnitTest裏面找到。
更多的測試例子,以及相關API的使用方法,請參考Mockito源碼裏的測試用例。