Mockito是一个mock框架。能够帮助我们使用更加简洁的API,写更漂亮、可读性更强的测试代码。
mock怎么理解?“模拟”。
就拿mvp架构来说。当你想要测试presenter的某个方法时,如果在该方法里,有调用到model层的方法。
而你要根据model层这个方法的返回值,或者产生的副作用,来检测presenter里面的代码。
这时候,Mockito就能派上用场了。你可以mock这个model的方法,或者返回任意你想要的值,或者让这个方法什么也不做(对无返回值的方法而言),甚至让这个方法抛各种异常,帮助你检测在各种不同的情况下,你所写的方法,是否都能按你设计的步骤顺利执行。
Mockito的集成
1.app的build.gradle下添加依赖
testImplementation "org.mockito:mockito-core:2.27.0"
复制代码
Mockito的使用
1.mock和spy
mock(Class
<T>
)、spy(Class
<T>
)、spy(Object)均会生成一个mock实例。
只有mock实例,才能调用Mockito的API来mock各种方法。不然会抛各种异常。
注意,文中的“mock实例”,除非特别强调。一般都泛指经由mock(Class)、spy(Class)或spy(Object)方法产生的实例。记笔记~
使用方法如下:
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;
private MockitoSample spySample;
@Before
public void setup(){
MockitoAnnotations.initMocks(this);
复制代码
1)spy(Class
<T>
)和spy(Object)
其中,由下面的部分Mockito源码可知,spy(Class
<T>
)、spy(Object)没有本质的区别。(所以,下文在介绍mock、spy区别的时候,为简便起见。只说spy(Class
<T>
)。)
public static <T> T spy(Class<T> classToSpy) {
return MOCKITO_CORE.mock(classToSpy, withSettings()
.useConstructor()
.defaultAnswer(CALLS_REAL_METHODS));
public static <T> T spy(T object) {
return MOCKITO_CORE.mock((Class<T>) object.getClass(), withSettings()
.spiedInstance(object)
.defaultAnswer(CALLS_REAL_METHODS));
复制代码
2)mock和spy的区别
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。
2.when...thenReturn...和doReturn...when...
Mockito提供了很多类似
when...thenReturn...
或者
doReturn...when...
的方法,都是常见的mock一个方法的手段。
很多时候,这两者是可以互相通用的,你可以选择使用
when...thenReturn...
,也可以选择使用
doReturn...when...
。如:
String expected = "mockPublicMethodReturnString";
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...
一运行也能立马检测出来错误。
when(mockSample.publicMethodReturnString()).thenReturn(1);
doReturn(1).when(mockSample).publicMethodReturnString();
复制代码
3)
when...thenReturn...
无法mock返回值为void的方法。而
doReturn...when...
可以。
doNothing().when(mockSample).publicMethodNoReturnThrowException();
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()
1)verify
主要用于验证一个方法被调用过多少次。使用示例代码:
@Test
public void verify_publicMethodReturnString() {
verify(mockSample, never()).publicMethodReturnString();
mockSample.publicMethodReturnString();
verify(mockSample).publicMethodReturnString();
mockSample.publicMethodReturnString();
verify(mockSample, times(2)).publicMethodReturnString();
复制代码
2)参数匹配器isA、anyXxx、eq
主要用于配合verify方法,验证方法的调用。使用示例代码:
@Test
public void verify_publicMethodCalculate() {
verify(mockSample, never()).publicMethodCalculate(1, 2);
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));
复制代码
Mockito的实战
看前面看得一脸懵逼?没关系,来实战一下吧~
下面演示如何测试常见的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() {
MockitoAnnotations.initMocks(this);
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、spy在实际运用时,该做何选择?
简单概括为:
1.要mock其他类的方法
这时使用mock(Class
<T>
)。比如,测试presenter,我们要mock掉model层的方法,一般都是使用mock(Class
<T>
)。但有人会说,如果我只想mock掉model层的部分方法,一些方法还是让它走真实逻辑呢?一般来说,不会这样子做,毕竟我们现在要测试的是presenter的方法,应该排除model的干扰,mock掉model层的方法。
2.要mock自身的成员方法
这时使用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 {
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源码
里的测试用例。