Mockito测试框架的使用
前面介绍的只能测试不涉及Android相关Api的java代码用例,如果涉及到Android相关Api的时候,就不方便了,这时如果不依赖第三方库的话可能需要使用仪器化测试跑到Android设备上去运行,于是有一些比较好的第三方的替代框架可以来模拟使用Android的代码测试,Mockito就是基于依赖注入实现的一个测试框架。
1. Mock概念的理解
什么是Mock, 这个单词的中文意思就是“模仿”或者“虚假”的意思,也就是要模仿一个对象,为啥要模仿?
在传统的JUnit单元测试中,没有消除在测试中对对象的依赖,如A对象依赖B对象方法,在测试A对象的时候,我们需要构造出B对象,这样子增加了测试的难度,或者使得我们对某些类的测试无法实现。这与单元测试的思路相违背。
还有一个主要的问题就是本地单元测试由于是运行本地JVM环境,无法依赖Android的api,只靠纯Junit的测试环境很难模拟出完整的Android环境,导致无法测试Android相关的代码,而Mock就能解决这个问题,通过Mock能够很轻易的实现对象的模拟。
添加依赖:
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) testImplementation 'org.mockito:mockito-core:2.19.0' .... } |
2. Mockito中几种Mock对象的方式
使用之前通过静态方式导入会使用更方便:
// 静态导入会使代码更简洁 import static org.mockito.Mockito.*; |
直接mock一个对象:
@Test public void testMock() { SimpleClass mockSimple = Mockito.mock(SimpleClass.class); assertNotNull(mockSimple); } |
注解方式mock一个对象:
@Mock SimpleClass simple; @Before public void setUp() { MockitoAnnotations.initMocks(this); } @Test public void testMock() { assertNotNull(simple); } |
运行器方式mock一个对象:
@RunWith(MockitoJUnitRunner.class) public class ExampleUnitTest { @Mock SimpleClass simple; @Test public void testMock() { assertNotNull(simple); } } |
MockitoRule方式mock一个对象:
public class ExampleUnitTest { @Mock SimpleClass simple; @Rule //<--使用@Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Test public void testMock() { assertNotNull(simple); } } |
3. 验证行为
·verify(T mock)函数的使用
verify(T mock)的作用是验证发生的某些行为等同于verify(mock, times(1)) 例如: @Test public void testMock() { //创建mock对象 List mockedList = mock(List.class); //使用mock对象 mockedList.add("one"); mockedList.clear(); //验证mockedList.add("one")是否被调用,如果被调用则当前测试方法通过,否则失败 verify(mockedList).add("one"); //验证 mockedList.clear()是否被调用,如果被调用则当前测试方法通过,否则失败 verify(mockedList).clear(); } |
@Test public void testMock() { mock.someMethod("some arg"); //验证mock.someMethod("some arg")是否被调用,如果被调用则测试方法通过,否则失败 verify(mock).someMethod("some arg"); } |
也就是说如果把调用的方法注释掉,则运行testMock()方法就会失败。
通过verify关键字,一旦mock对象被创建了,mock对象会记住所有的交互。然后你就可能选择性的验证你感兴趣的交互。
通常需要配合一些测试方法来验证某些行为,这些方法称为"打桩方法"(Stub),打桩的意思是针对mock出来的对象进行一些模拟操作,如设置模拟的返回值或抛出异常等。
常见的打桩方法:
例如:
@Test public void testMock() { // 你可以mock具体的类型,不仅只是接口 List mockedList = mock(List.class); // 打测试桩 when(mockedList.get(0)).thenReturn("first"); doReturn("aaaa").when(mockedList).get(1); when(mockedList.get(1)).thenThrow(new RuntimeException()); doThrow(new RuntimeException()).when(mockedList).clear(); // 输出“first” System.out.println(mockedList.get(0)); // 因为get(999) 没有打桩,因此输出null, 注意模拟环境下这个地方是不会报IndexOutOfBoundsException异常的 System.out.println(mockedList.get(999)); // get(1)时会抛出异常 System.out.println(mockedList.get(1)); // clear会抛出异常 mockedList.clear(); } |
doXXX和thenXXX使用上差不多,一个是调用方法之前设置好返回值,一个是在调用方法之后设置返回值。默认情况下,Mock出的对象的所有非void函数都有返回值,对象类型的默认返回的是null,例如返回int、boolean、String的函数,默认返回值分别是0、false和null。
·使用when(T methodCall)函数
打桩方法需要配合when(T methodCall)函数,意思是使测试桩方法生效。当你想让这个mock能调用特定的方法返回特定的值,那么你就可以使用它。
例如:
when(mock.someMethod()).thenReturn(10); //你可以使用灵活的参数匹配,例如 when(mock.someMethod(anyString())).thenReturn(10); //设置抛出的异常 when(mock.someMethod("some arg")).thenThrow(new RuntimeException()); //你可以对不同作用的连续回调的方法打测试桩: //最后面的测试桩(例如:返回一个对象:"foo")决定了接下来的回调方法以及它的行为。 when(mock.someMethod("some arg")) .thenReturn("foo")//第一次调用someMethod("some arg")会返回"foo" .thenThrow(new RuntimeException());//第二次调用someMethod("some arg")会抛异常 //可以用以下方式替代比较小版本的连贯测试桩: when(mock.someMethod("some arg")) .thenReturn("one", "two"); //和下面的方式效果是一样的 when(mock.someMethod("some arg")) .thenReturn("one") .thenReturn("two"); //比较小版本的连贯测试桩并且抛出异常: when(mock.someMethod("some arg")) .thenThrow(new RuntimeException(), new NullPointerException(); |
·使用thenAnswer为回调做测试桩
when(mock.someMethod(anyString())).thenAnswer(new Answer() { Object answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); Object mock = invocation.getMock(); return "called with arguments: " + args; } }); // 输出 : "called with arguments: foo" System.out.println(mock.someMethod("foo")); |
·使用doCallRealMethod()函数来调用某个方法的真实实现方法
注意,在Mock环境下,所有的对象都是模拟出来的,而方法的结果也是需要模拟出来的,如果你没有为mock出的对象设置模拟结果,则会返回默认值,例如:
public class Person { public String getName() { return "小明"; } } @Test public void testPerson() { Person mock = mock(Person.class); //输出null,除非设置发回模拟值when(mock.getName()).thenReturn("xxx"); System.out.println(mock.getName()); } |
因为getName()方法没有设置模拟返回值,而getName()返回值是String类型的,因此直接调用的话会返回String的默认值null,所以上面代码如果要想输出getName()方法的真实返回值的话,需要设置doCallRealMethod():
@Test public void testPerson() { Person mock = mock(Person.class); doCallRealMethod().when(mock).getName(); //输出“小明” System.out.println(mock.getName()); } |
·使用doNothing()函数是为了设置void函数什么也不做
需要注意的是默认情况下返回值为void的函数在mocks中是什么也不做的但是,也会有一些特殊情况。如:
测试桩连续调用一个void函数时:
doNothing().doThrow(new RuntimeException()).when(mock).someVoidMethod(); //does nothing the first time: mock.someVoidMethod(); //throws RuntimeException the next time: mock.someVoidMethod(); |
监控真实的对象并且你想让void函数什么也不做:
List list = new LinkedList(); List spy = spy(list); //let's make clear() do nothing doNothing().when(spy).clear(); spy.add("one"); //clear() does nothing, so the list still contains "one" spy.clear(); |
·使用doAnswer()函数测试void函数的回调
当你想要测试一个无返回值的函数时,可以使用一个含有泛型类Answer参数的doAnswer()函数做回调测试。假设你有一个void方法有多个回调参数,当你想指定执行某个回调时,使用thenAnswer很难实现了,如果使用doAnswer()将非常简单,示例代码如下:
MyCallback callback = mock(MyCallback.class); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { //获取第一个参数 MyCallback call = invocation.getArgument(0); //指定回调执行操作 call.onSuccess(); return null; } }).when(mockedObject.requset(callback)); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { System.out.println("onSuccess answer"); return null; } }).when(callback).onSuccess(); mockedObject.requset(callback) |
·需要使用doReturn函数代替thenReturn的情况
如当监控真实的对象并且调用真实的函数带来的影响时
List list = new LinkedList(); List spy = spy(list); //不可能完成的:真实方法被调用的时候list仍是空的,所以spy.get(0)会抛出IndexOutOfBoundsException()异常 when(spy.get(0)).thenReturn("foo"); //这时你应该使用doReturn()函数 doReturn("foo").when(spy).get(0); |
·使用doThrow()函数来测试void函数抛出异常
SimpleClass mock = mock(SimpleClass.class); doThrow(new RuntimeException()).when(mock).someVoidMethod(); mock.someVoidMethod(); |
总之使用doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 这些函数时可以在适当的情况下调用when()来解决一些问题., 如当你需要下面这些功能时这是必须的:
·测试void函数
·在受监控的对象上测试函数
·不只一次的测试同一个函数,在测试过程中改变mock对象的行为
4. 验证方法的调用次数
需要配合使用一些方法:
例如:
mock.someMethod("some arg"); mock.someMethod("some arg"); //验证mock.someMethod("some arg")被连续调用两次,即如果没有调用两次则验证失败 verify(mock, times(2)).someMethod("some arg"); |
//注意,下面三种是等价的,都是验证someMethod()被只调用一次 verify(mock).someMethod("some arg"); verify(mock, times(1)).someMethod("some arg"); verify(mock, only()).someMethod("some arg"); |
mPerson.getAge(); mPerson.getAge(); //验证至少调用2次 verify(mPerson, atLeast(2)).getAge(); //验证至多调用2次 verify(mPerson, atMost(2)).getAge(); |
//下面两种等价,验证调用次数为0 verify(mPerson, never()).getAge(); verify(mPerson, times(0)).getAge(); |
mPerson.getAge(); mPerson.getAge(); long current = System.currentTimeMillis(); System.out.println(current ); //延时1s后验证mPerson.getAge()是否被执行了2次 verify(mPerson, after(1000).times(2)).getAge(); System.out.println(System.currentTimeMillis() - current); |
mPerson.getAge(); mPerson.getAge(); //验证方法在100ms超时前被调用2次 verify(mPerson, timeout(100).times(2)).getAge(); |
@Test public void testVerifyZeroInteractions() { Person person = mock(Person.class); person.eat("a"); //由于person对象发生了交互,所以这里验证失败,把上面的调用注释掉这里就会验证成功 verifyZeroInteractions(person); //可以验证多个对象没有交互 //verifyZeroInteractions(person,person2 ); } |
@Test public void testVerifyZeroInteractions() { Person person = mock(Person.class); person.eat("a"); verify(person).eat("a"); //注意,这将会无法到达验证目的,不能跟verify()混用 verifyZeroInteractions(person,person2 ); } |
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理