Mockito官网
Mockito是一个mock框架。能够帮助我们使用更加简洁的API,写更漂亮、可读性更强的测试代码。
mock怎么理解?“模拟”。
就拿mvp架构来说。当你想要测试presenter的某个方法时,如果在该方法里,有调用到model层的方法。 而你要根据model层这个方法的返回值,或者产生的副作用,来检测presenter里面的代码。
这时候,Mockito就能派上用场了。你可以mock这个model的方法,或者返回任意你想要的值,或者让这个方法什么也不做(对无返回值的方法而言),甚至让这个方法抛各种异常,帮助你检测在各种不同的情况下,你所写的方法,是否都能按你设计的步骤顺利执行。
testImplementation "org.mockito:mockito-core:2.27.0" 复制代码
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; @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>
)。)
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源码 里的测试用例。