学习单元测试 Mockito 一篇文章就够了
文章目录
- 一、什么是 Mockito
- 二、为什么使用 Mockito
- 三、Mockito 基本使用
- 1. 导入 Maven 依赖
- 2. 创建Mock对象
- 3. 配置 Mock 对象
- 4. Mock 抛出异常
- 四、检验 Mock 对象的方法调用
- 五、使用 spy() 部分模拟对象
- 六、常用操作
- 1. 验证mock对象是否执行了某些操作
- 2. 打桩
- 3. 参数匹配器
- 4. 验证方法的调用次数/最多/最少/从不等
- 5. 返回值为void的方法调用时抛异常打桩
- 6. 验证执行顺序
- 7. 验证没有任何交互
- 8. 检查是否还有没有验证的交互
- 9. 简化Mock创建,注解驱动
- 10. 连续打桩
- 11. 回调打桩
- 12. doReturn()、doThrow()、doAnswer()、doNothing()、doCallRealMethod()系列方法的运用
- 13. spy 监视真实的对象
- 14. detail
- 15. 自定义失败打印信息
- 16. lambda支持
一、什么是 Mockito
Mockito 是一个强大的用于 Java 开发的模拟测试框架, 通过 Mockito 我们可以创建和配置 Mock 对象, 进而简化有外部依赖的类的测试.
使用 Mockito 的大致流程如下:
- 创建外部依赖的 Mock 对象, 然后将此 Mock 对象注入到测试类中.
- 执行测试代码.
- 校验测试代码是否执行正确
二、为什么使用 Mockito
假设我们正在编写一个银行的服务 BankService, 这个服务的依赖关系如下:
当我们需要测试 BankService 服务时, 该真么办呢?
一种方法是构建真实的 BankDao, DB, AccountService 和 AuthService 实例, 然后注入到 BankService 中.
不用我说, 读者们也肯定明白, 这是一种既笨重又繁琐的方法, 完全不符合单元测试的精神. 那么还有一种更加优雅的方法吗?
自然是有的, 那就是我们今天的主角 Mock Object. 下面来看一下使用 Mock 对象后的框架图:
我们看到, BankDao, AccountService 和 AuthService 都被我们使用了虚拟的对象(Mock 对象) 来替换了, 因此我们就可以对 BankService 进行测试, 而不需要关注它的复杂的依赖了.
三、Mockito 基本使用
1. 导入 Maven 依赖
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.4.0</version><!--根据情况添加作用范围--><scope>compile</scope>
</dependency>
2. 创建Mock对象
@Test
public void createMockObject() {// 使用 mock 静态方法创建 Mock 对象.List mockedList = mock(List.class);Assert.assertTrue(mockedList instanceof List);// mock 方法不仅可以 Mock 接口类, 还可以 Mock 具体的类型.ArrayList mockedArrayList = mock(ArrayList.class);Assert.assertTrue(mockedArrayList instanceof List);Assert.assertTrue(mockedArrayList instanceof ArrayList);
}
如上代码所示, 我们调用了 mock 静态方法来创建一个 Mock 对象. mock 方法接收一个 class 类型, 即我们需要 mock 的类型.
3. 配置 Mock 对象
当我们有了一个 Mock 对象后, 我们可以定制它的具体的行为. 例如:
import org.junit.Assert;
import org.junit.Test;
import java.util.Iterator;
import java.util.List;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;public class TestJunit {@Testpublic void configMockObject() {List mockedList = mock(List.class);// 我们定制了当调用 mockedList.add("one") 时, 返回 truewhen(mockedList.add("one")).thenReturn(true);// 当调用 mockedList.size() 时, 返回 1when(mockedList.size()).thenReturn(1);Assert.assertTrue(mockedList.add("one"));// 因为我们没有定制 add("two"), 因此返回默认值, 即 false.Assert.assertFalse(mockedList.add("two"));Assert.assertEquals(mockedList.size(), 1);Iterator i = mock(Iterator.class);when(i.next()).thenReturn("Hello,").thenReturn("Mockito!");String result = i.next() + " " + i.next();//assertAssert.assertEquals("Hello, Mockito!", result);}
}
我们使用 when(...).thenReturn(...)
方法链来定义一个行为, 例如
“when(mockedList.add("one")).thenReturn(true)
” 表示: 当调用了mockedList.add("one")
那么返回 true…
并且要注意的是, when(...).thenReturn(...)
方法链不仅仅要匹配方法的调用, 而且要方法的参数一样才行.
而且有趣的是, when(...).thenReturn(...)
方法链可以指定多个返回值, 当这样做后, 如果多次调用指定的方法, 那么这个方法会依次返回这些值.
例如 “when(i.next()).thenReturn("Hello,").thenReturn("Mockito!");
”, 这句代码表示: 第一次调用 i.next()
时返回 “Hello,”, 第二次调用 i.next()
时返回 “Mockito!”.
4. Mock 抛出异常
上面的例子我们展示了方法调用返回值的定制, 那么我们可以指定一个抛出异常吗?
当然可以的, 例如:
@Test(expected = NoSuchElementException.class)
public void testForIOException() throws Exception {Iterator i = mock(Iterator.class);when(i.next()).thenReturn("Hello,").thenReturn("Mockito!"); // 1String result = i.next() + " " + i.next(); // 2Assert.assertEquals("Hello, Mockito!", result);doThrow(new NoSuchElementException()).when(i).next(); // 3i.next(); // 4
}
上面代码的第一第二步我们已经很熟悉了, 接下来第三部我们使用了一个新语法:
doThrow(ExceptionX).when(x).methodCall
, 它的含义是: 当调用了 x.methodCall 方法后, 抛出异常 ExceptionX.
因此 doThrow(new NoSuchElementException()).when(i).next()
的含义就是: 当第三次调用 i.next() 后, 抛出异常 NoSuchElementException.(因为 i 这个迭代器只有两个元素)
四、检验 Mock 对象的方法调用
Mockito 会追踪 Mock 对象的所用方法调用和调用方法时所传递的参数. 我们可以通过 verify() 静态方法来来校验指定的方法调用是否满足断言. 语言描述有一点抽象, 下面我们仍然以代码来说明一下.
@Test
public void testVerify() {List mockedList = mock(List.class);mockedList.add("one");mockedList.add("two");mockedList.add("three times");mockedList.add("three times");mockedList.add("three times");when(mockedList.size()).thenReturn(5);Assert.assertEquals(mockedList.size(), 5);verify(mockedList, atLeastOnce()).add("one");verify(mockedList, times(1)).add("two");verify(mockedList, times(3)).add("three times");verify(mockedList, never()).isEmpty();
}
上面的例子前半部份没有什么特别的, 我们关注后面的:
verify(mockedList, atLeastOnce()).add("one");
verify(mockedList, times(1)).add("two");
verify(mockedList, times(3)).add("three times");
verify(mockedList, never()).isEmpty();
读者根据代码也应该可以猜测出它的含义了, 很简单:
- 第一句校验 mockedList.add(“one”) 至少被调用了 1 次(atLeastOnce)
- 第二句校验 mockedList.add(“two”) 被调用了 1 次(times(1))
- 第三句校验 mockedList.add(“three times”) 被调用了 3 次(times(3))
- 第四句校验 mockedList.isEmpty() 从未被调用(never)
五、使用 spy() 部分模拟对象
Mockito 提供的 spy 方法可以包装一个真实的 Java 对象, 并返回一个包装后的新对象. 若没有特别配置的话, 对这个新对象的所有方法调用, 都会委派给实际的 Java 对象. 例如:
@Test
public void testSpy() {List list = new LinkedList();List spy = spy(list);// 对 spy.size() 进行定制.when(spy.size()).thenReturn(100);spy.add("one");spy.add("two");// 因为我们没有对 get(0), get(1) 方法进行定制,// 因此这些调用其实是调用的真实对象的方法.Assert.assertEquals(spy.get(0), "one");Assert.assertEquals(spy.get(1), "two");Assert.assertEquals(spy.size(), 100);
}
这个例子中我们实例化了一个 LinkedList 对象, 然后使用 spy() 方法对 list 对象进行部分模拟.
接着我们使用 when(...).thenReturn(...)
方法链来规定 spy.size()
方法返回值是 100. 随后我们给 spy 添加了两个元素, 然后再 调用 spy.get(0)
获取第一个元素.
这里有意思的地方是: 因为我们没有定制 add(“one”), add(“two”), get(0), get(1), 因此通过 spy 调用这些方法时, 实际上是委派给 list 对象来调用的.
然而我们 定义了 spy.size() 的返回值, 因此当调用 spy.size() 时, 返回 100.
六、常用操作
1. 验证mock对象是否执行了某些操作
list.add(1);list.add(2);Mockito.verify(list).add(1);//验证通过Mockito.verify(list).add(5);//验证未通过,因为没有执行过该操作
2. 打桩
打桩就是模拟一些函数调用的反应。
Mockito.when(list.get(1)).thenReturn(3);Assert.assertEquals(list.get(1),3);Mockito.when(list.get(1)).thenReturn(4);Assert.assertEquals(list.get(1),4);
默认情况下,对于有返回值的方法,Mock返回null,原始/原始包装器值或空集合。
例如,对于int/Integer为0,对于boolean/Boolean为false。
可以进行多次打桩,但是以最后一次为准。
一次打桩,可多次调用
3. 参数匹配器
Mockito.when(list.get(Mockito.anyInt())).thenReturn("hi");//只要传入任何int,返回hiAssert.assertEquals("hi",list.get(1));//hiAssert.assertEquals("hi",list.get(999));//hiMockito.when(list.contains(Mockito.isA(One.class))).thenReturn(true);//只要传入String类型,就返回helloAssert.assertTrue(list.contains("hello"));//helloAssert.assertTrue(list.contains(1));//发生错误
参数匹配器是为了更加灵活的进行验证和打桩,可以自定义
注意:如果在一个方法中使用参数匹配器,那么该方法的所有参数都要使用参数匹配器,验证和打桩都是如此,例如
A a=Mockito.mock(A.class);//Mockito.when(a.add(Mockito.anyInt(),1)).thenReturn(2);//出错Mockito.when(a.add(Mockito.anyInt(),Mockito.anyInt())).thenReturn(2);//成功
4. 验证方法的调用次数/最多/最少/从不等
不传入该参数,默认是1
public void verifyNumber(){List list=Mockito.mock(List.class);list.add(1);list.add(1);list.add(1);list.add(1);list.remove(3);list.remove(3);list.remove(3);list.get(3);//验证调用次数Mockito.verify(list,Mockito.times(4)).add(1);Mockito.verify(list,Mockito.times(3)).remove(3);Mockito.verify(list,Mockito.times(0)).add(0);Mockito.verify(list).add(1);//异常Mockito.verify(list,Mockito.atLeast(1)).add(1);//至少Mockito.verify(list,Mockito.atMost(5)).add(1);//之多Mockito.verify(list,Mockito.never()).get(0);//没有调用Mockito.verify(list,Mockito.atMostOnce()).get(3);//最多}
5. 返回值为void的方法调用时抛异常打桩
如下测试通过
@Test(expected = RuntimeException.class)public void throwTest(){List list=Mockito.mock(List.class);//此处抛异常Mockito.doThrow(new RuntimeException("异常")).when(list).add(1,1);list.add(1, 1);}
6. 验证执行顺序
public void orderTest(){List one = Mockito.mock(List.class);one.add(1);one.add(4);one.add(2);//创建一个顺序验证器InOrder inOrder=Mockito.inOrder(one);//验证执行顺序,不一致则抛异常inOrder.verify(one).add(1);inOrder.verify(one).add(2);//验证多个mock的调用顺序List list=Mockito.mock(List.class);A a=Mockito.mock(A.class);list.add(1);a.add(1,2);InOrder inOrder1=Mockito.inOrder(list,a);inOrder1.verify(list).add(1);inOrder1.verify(a).add(1,2);}
先后顺序一致则可,不必死板的验证每一次调用
7. 验证没有任何交互
List list=Mockito.mock(List.class);list.add(1);List list2=Mockito.mock(List.class);List list3=Mockito.mock(List.class);Mockito.verifyNoInteractions(list2,list3);Mockito.verifyNoInteractions(list,list2);//异常
8. 检查是否还有没有验证的交互
它用来检查有么有冗余的调用
List list = Mockito.mock(List.class);list.add(1);Mockito.verify(list).add(1);Mockito.verifyNoMoreInteractions(list);list.add(2);Mockito.verifyNoMoreInteractions(list);//异常
9. 简化Mock创建,注解驱动
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {//注解取代Mockito.mock(A.class)@Mockprivate A myA;//在使用注解前需要执行MockitoAnnotations.openMocks(testClass.class);//否则无法使用@BeforeClasspublic static void start(){MockitoAnnotations.openMocks(MockitoTest.class);}@Testpublic void testAnnotation(){myA.abc();}
}
10. 连续打桩
Demo mock=Mockito.mock(Demo.class);Mockito.when(mock.someMethod("some arg")).thenReturn("one", "two", "three");System.out.println(mock.someMethod("some arg"));//oneSystem.out.println(mock.someMethod("some arg"));//twoSystem.out.println(mock.someMethod("some arg"));//threeSystem.out.println(mock.someMethod("some arg"));//three
最后的将持续下去
11. 回调打桩
Demo demo=Mockito.mock(Demo.class);Mockito.when(demo.someMethod(Mockito.any())).thenAnswer(new Answer<Object>() {@Overridepublic Object answer(InvocationOnMock invocation) throws Throwable {Object[] arguments = invocation.getArguments();Object mock = invocation.getMock();return mock.toString()+" "+arguments.length+" "+arguments;}});System.out.println(demo.someMethod("he"));
12. doReturn()、doThrow()、doAnswer()、doNothing()、doCallRealMethod()系列方法的运用
对于有返回值的方法进行打桩Mockito.when(instance.someMethod()).thenReturn(xxx)
,但是对于没有返回值的方法却不能放在括号内当参数传递,因此对于它们有不同的方法进行打桩。
@Test(expected = RuntimeException.class)
public void voidTest() {List list=Mockito.mock(List.class);Mockito.doThrow(new RuntimeException()).when(list).clear();list.clear();
}
13. spy 监视真实的对象
在spy上执行方法时如果没有打桩,那么在真实的对象上也会执行该方法,尽量少用
public void spyTest(){List list=new LinkedList();List spy = Mockito.spy(list);spy.add("a");spy.add("b");Mockito.when(spy.size()).thenReturn(5);System.out.println(spy.size());System.out.println(list.size());}
14. detail
获取mock的详情
public void detailTest(){List list = Mockito.mock(List.class);list.add(1);Mockito.verify(list).add(1);Mockito.verifyNoMoreInteractions(list);list.add(2);MockingDetails mockingDetails = Mockito.mockingDetails(list);System.out.println(mockingDetails.getInvocations());System.out.println(mockingDetails.getStubbings());System.out.println(mockingDetails.isMock());System.out.println(mockingDetails.isSpy());
}
/*
[list.add(1);, list.add(2);]
[]
true
false
*/
15. 自定义失败打印信息
public void desc(){List list=Mockito.mock(List.class);list.add(1);Mockito.verify(list,Mockito.description("失败了")).add(1);Mockito.verify(list,Mockito.description("失败了")).add(2);}
16. lambda支持
verify(list, times(2)).add(argThat(string -> string.length() < 5));//等同于verify(list, times(2)).add(argThat(new ArgumentMatcher(){public boolean matches(String arg) {return arg.length() < 5;}}));
【参考】
【1】https://blog.csdn.net/gentlezuo/article/details/108293961
【2】https://segmentfault.com/a/1190000006746409
学习单元测试 Mockito 一篇文章就够了相关推荐
- ES学习看这一篇文章就够了
第一章 ES简介 第1节 ES介绍 1 2 3 4 1.Elasticsearch是一个基于Lucene的搜索服务器 2.提供了一个分布式的全文搜索引擎,基于restful web接口 3.Elast ...
- python qtextedit设置光标位置_Python基础命令学习——就这一篇文章就够了
一.python的定义: python是一种计算机程序设计语言,是一种解释型.编程型的脚本语言. 发现有很多想要学习Python却不知道如何下手的朋友,我这里整理了一些关于Python的学习资料,从基 ...
- Android:学习AIDL,这一篇文章就够了(下)
前言 上一篇博文介绍了关于AIDL是什么,为什么我们需要AIDL,AIDL的语法以及如何使用AIDL等方面的知识,这一篇博文将顺着上一篇的思路往下走,接着介绍关于AIDL的一些更加深入的知识.强烈建议 ...
- 学习 HTML5 Canvas 这一篇文章就够了
一.canvas 简介 <canvas> 是 HTML5 新增的,一个可以使用脚本(通常为 JavaScript) 在其中绘制图像的 HTML 元素.它可以用来制作照片集或者制作简单(也 ...
- 学习 MongoDB 一篇文章就够了(珍藏版)
文章目录 一.学习目录 二.扩展目录 一.学习目录 认识 MongoDB 一篇文章就够了 Windows平台安装MongoDB教程 Linux 上安装 MongoDB windows 安装 Mongo ...
- 全面认识二极管,一篇文章就够了
电子设计基础元器件 二极管,小小二极管,大大用途. ... 矜辰所致 目录 前言 一.二极管基础知识 1.1 什么是二极管 1.2 二极管的组成 1.3 二极管的原理 二.二极管特性 2.1 伏安特性 ...
- 关于VR产品的前世今生,看这一篇文章就够了
关于VR产品的前世今生,看这一篇文章就够了(转) 文/胡勇 即使最富质疑精神最冷静的人也无法漠视现在的 VR/AR 掀起的狂潮,这个从科技圈蔓延到实业界最后席卷大众的想象力的狂欢正以前所未有的态势改变 ...
- 五年程序员是如何玩转闲鱼无货源的,只看这一篇文章就够了
今天的内容方向主要是基础篇-进阶篇 ,优化了一下操作方法,尽量细化,让你看完这篇内容之后从入门到大神. 基础篇: 注册这些基础的之前说过,这次就不说了,这次说下如何养号. 完善个人资料(头像.昵称.简 ...
- 如何使用 Maven 来创建项目(一篇文章就够了)
如何使用 Maven 来创建项目(一篇文章就够了) 1. Maven 简介 1. 简介 2. 项目构建 3. 项目构建工具 2. Maven 的四大特性 1. 依赖管理系统 版本号规范 2. 多模块构 ...
最新文章
- 《自然》,工程学突破!仿生物细胞群体机器人问世
- 旷视冲刺科创板IPO获通过!距离上市仅一步之遥,拟募资60.18亿
- socket-tcp 、udp、rawIP
- Java中System.setProperty()用法
- 第三讲:Asp.Net+Autofac+EF/ADO.NET Winform OA(3)-启用DevExpress皮肤功能
- MySQL日期与时间函数
- cdn dashjs_CSS以及JS各种库的在线CDN引用地址
- 矩阵分析与应用课后答案——张贤达版本
- 去掉iframe的水平滚动条而保留垂直滚动条
- G盘文件系统RAW要怎么办啊
- 新媒体营销操作手法及案例分享-初贵民
- oracle Dataguard数据库不同步处理备忘
- 使用MurMurHash在Shodan平台上寻找钓鱼网站
- Vim编辑器常用命令
- 2019春招奇虎360玫瑰花摆放
- matlab投资组合权重,马科维茨投资组合理论(均方模型)学习笔记――基于Matlab(四)...
- 【Java 8 新特性】Java LocalDate 和 Epoch 互相转换
- 分享程序员面试的7个技巧
- 变分法求解两点间直线距离最短
- 皮革店铺怎么实施IT程序快速实施 部署