日常工作中很多时候都需要同事间的相互配合协作完成某些功能,所以经常会遇到服务或者应用内不同模块之间要互相依赖的场景。比如下面的场景,serviceA 中的 methodA() 方式依赖 serviceB 中的 methodB() 方法返回操作的结果。那如果要对自己的methodA() 方法进行编写单元测试,还需要等其他同事的methodB() 方法开发完成才行。那有没有什么办法可以跳过或者说模拟方法 B 的输出呢?这就引出了今天的主角 Mockito,一个优秀的 Mock 测试框架。
通过使用 Mock 技术可以让开发不停滞,Mock技术的作用是将服务与服务之间的依赖在测试自测阶段隔离开,让开发人员在自己的应用内部通过模拟的方式把需要依赖外部的接口给构造出来,从而保证不被外界的开发进度所影响。Mockito 就是一个优秀的 Mock 框架。
Mockito
Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produce clean verification errors.
Mockito 是一个很好用的模拟框架。它可以使用干净简单的 API 编写漂亮的测试。Mockito 的可读性非常好,不会让人感动迷惑,产生的验证错误也很明确。
官网地址:https://site.mockito.org/
中文文档:https://github.com/hehonghui/mockito-doc-zh#0
测试用例 1
首先在工程的 pom 文件里面加依赖,加上 mockito 和 junit 的依赖。
<dependency><groupId>org.mockito</groupId><artifactId>mockito-all</artifactId><version>1.9.5</version><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency>
接下来编写一个简单的测试用例,这里通过mock 一个 List 对象,先添加几个元素,后面验证添加交互是否与预期的一致。
@Testpublic void testVerify() throws Exception {//创建 mock 对象List mockedList = mock(List.class);mockedList.add("test1");mockedList.add("test2");mockedList.add("test2");mockedList.clear();//验证是否执行了一次 add("test1") 操作verify(mockedList).add("test1");//同上面验证是否执行了一次 add("test1") 操作,默认就是 time(1)verify(mockedList, times(1)).add("test1");//验证是否执行了3次 add("test2") 操作//verify(mockedList, times(3)).add("test2");verify(mockedList).clear();}
上面的测试用例运行过后是如下效果,测试用例是通过的。
当放开verify(mockedList, times(3)).add("test2"); 这一行代码进行运行时,可以看到测试用例未通过,提示的错误是预期执行 3 次,结果实际只执行了 2 次add("test2") 操作。
上面的测试用例是验证对应方式的执行次数是否和预期一致,除了有准确的次数之外,还有最多,至少,从未等验证方式,如下所示:
//精确次数verify(mockedList, times(3)).add("test2");//至少 1次verify(mockedList, atLeastOnce()).add("test2");//至少 2 次verify(mockedList, atLeast(2)).add("test2");//最多 5 次verify(mockedList, atMost(5)).add("test2");
测试用例 2
通过设值或者打桩的方式预设参数,如下所示,当执行 get(0) 操作时,通过 thenReturn()方法返回 hello,当执行 get(1)操作时抛出空指针异常,运行结果如下图所示:
@Testpublic void testWhen() throws Exception {LinkedList mockedList = mock(LinkedList.class);//设置值,通常被称为打桩when(mockedList.get(0)).thenReturn("hello");when(mockedList.get(1)).thenThrow(new NullPointerException());System.out.println(mockedList.get(0));//这里会打印 "null" 因为 get(2) 没有设置System.out.println(mockedList.get(2));//这里会抛 exceptionSystem.out.println(mockedList.get(1));//验证有没有执行 get(0) 操作verify(mockedList).get(0);}

可以看到当调用 get(0) 和 get(1) 的时候控制台成功的抛出了异常。这种方式通常被称为Stubbing,除了使用 when...thenReturn 方式之外,还有一种形式可以表达,代码如下:
@Testpublic void testDoReturn() throws Exception {Iterator mockedList = mock(Iterator.class);doReturn("hello").when(mockedList).next();Object next = mockedList.next();System.out.println(next);doReturn("world").when(mockedList).next();Object next2 = mockedList.next();System.out.println(next2);//上面的过程也可以写成如下方式doReturn("test1", "test2").when(mockedList).next();Object next3 = mockedList.next();System.out.println(next3);Object next4 = mockedList.next();System.out.println(next4);}
运行结果如下所示,也可以用 doThrow() 方法进行抛异常:
测试用例 3
日常开发中通过要保证方法的时效性,或者说要保证某个方法必须在多长时间内执行完成,这个时候也可以通过 mock 的方式来验证方法是否满足要求。代码如下:
@Testpublic void testTimeout() throws Exception {HttpService mock = mock(HttpService.class);String url = "http://www.xxx.com";mock.getRequest(url);verify(mock, timeout(100)).getRequest(url);//timeout时间后,用自定义的检验模式验证getRequest()VerificationMode customVer = new VerificationMode() {@Overridepublic void verify(VerificationData data) {}@Overridepublic VerificationMode description(String s) {return null;}};verify(mock, new Timeout(100, customVer)).getRequest(url);}
Mockito 还有很多 API 可以使用,更多的使用方式,可以参考这面这个网站。https://www.tutorialspoint.com/mockito/mockito_timeouts.htm,有更详细的介绍。
