一.简介
Mockito是mocking框架,它让你用简洁的API做测试,简单易学,可读性强并且验证语法简洁。
官网: http://mockito.org
项目源码:https://github.com/mockito/mockito
官方文档:https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/Mockito.html
PS:文中所有用例都由官方文档翻译改编而来
1. 为什么需要Mock
测试驱动的开发( TDD)要求我们先写单元测试,再写实现代码。在写单元测试的过程中,我们往往会遇到要测试的类有很多依赖,这些依赖的类/对象/资源又有别的依赖,从而形成一个大的依赖树,要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。如下图所示:
为了测试类A,我们需要Mock B类和C类(用虚拟对象来代替)如下图所示:
2. mock和Mockito的关系
在软件开发中提及”mock”,通常理解为模拟对象。
为什么需要模拟? 在我们一开始学编程时,我们所写的对象通常都是独立的,并不依赖其他的类,也不会操作别的类。但实际上,软件中是充满依赖关系的,比如我们会基于service类写操作类,而service类又是基于数据访问类(DAO)的,依次下去,形成复杂的依赖关系。
单元测试的思路就是我们想在不涉及依赖关系的情况下测试代码。这种测试可以让你无视代码的依赖关系去测试代码的有效性。核心思想就是如果代码按设计正常工作,并且依赖关系也正常,那么他们应该会同时工作正常。
有些时候,我们代码所需要的依赖可能尚未开发完成,甚至还不存在,那如何让我们的开发进行下去呢?使用mock可以让开发进行下去,mock技术的目的和作用就是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界以外的对象隔离开。
我们可以自己编写自定义的Mock对象实现mock技术,但是编写自定义的Mock对象需要额外的编码工作,同时也可能引入错误。现在实现mock技术的优秀开源框架有很多,Mockito就是一个优秀的用于单元测试的mock框架。Mockito已经在github上开源,详细请点击:https://github.com/mockito/mockito
除了Mockitob已被广泛使用以外,还有一些类似的框架,比如:
- EasyMock:早期比较流行的MocK测试框架。它提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常
- PowerMock:这个工具是在EasyMock和Mockito上扩展出来的,目的是为了解决EasyMock和Mockito不能解决的问题,比如对static, final, private方法均不能mock。其实测试架构设计良好的代码,一般并不需要这些功能,但如果是在已有项目上增加单元测试,老代码有问题且不能改时,就不得不使用这些功能了
- JMockit:JMockit 是一个轻量级的mock框架是用以帮助开发人员编写测试程序的一组工具和API,该项目完全基于 Java 5 SE 的 java.lang.instrument 包开发,内部使用 ASM 库来修改Java的Bytecode
二. 如何使用
1. 添加依赖
Mockitod 的maven依赖
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.8.47</version>
</dependency>
Mockitod 通常会结合junit使用
<dependency>
<groupId>org.junit</groupId>
<artifactId>com.springsource.org.junit</artifactId>
<version>4.8.2</version>
</dependency>
lombok 依赖,方便实现构造方法和get/set方法
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
在测试类中导入Mockito类方便使用Mockito类的静态方法
import static org.mockito.Mockito.*;
2. 创建mock代理类
2.1 使用静态方法创建
Mocmokito使用cglib动态代理代理要模拟的类,调用静态方法Mockito.mock()传入类对象即可
Iterator iterator = mock(Iterator.class);
一个标准的mock使用过程如下:
@Test
public void demo(){
//mock一个Iterator类,也可以是一个list类,或者你要模拟的类
Iterator iterator = mock(Iterator.class);
//预设当iterator调用next()时第一次返回hello,第n次都返回world
when(iterator.next()).thenReturn("hello").thenReturn("world");
//使用mock的对象
String result = iterator.next() + " " + iterator.next() + " " + iterator.next();
//验证结果
assertEquals("hello world world",result);
}
2.2 使用@Mock注解创建
使用注解创建模拟类更加简单,可读性好。
唯一需要注意的是,需要在@Before中调用MockitoAnnotations.initMocks(this);
public class ArticleManagerTest {
@Mock private ArticleCalculator calculator;
@Mock private ArticleDatabase database;
@Mock private UserProvider userProvider;
@Before
public void init(){
//初始化带有mock注解的对象@Mock,@Spy,@Captor,@InjectMocks
MockitoAnnotations.initMocks(this);
}
private ArticleManager manager;
}
除了调用MockitoAnnotations.initMocks(this)
方法外,还可以给类加上mock启动注解来实现
更多说明详见:MockitoJUnitRunner类
@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
@Mock
private List list;
@Test
public void shouldDoSomething() {
list.add(100);
}
}
添加这两者除了可以初始化mock类外,还可以做额外的验证,当出现Mocmokito语法错误时会在运行时报错
when(myMock.method1()); // 错误写法, 会报错
when(myMock.method1()).thenReturn(1); // 正确写法,当myMock类调用method1时返回1
2.3 使用@InjectMocks注解自动注入
Mockito将尝试仅通过构造函数注入,setter注入或属性名注入按顺序注入模拟。如果以上任何策略失败,则Mockito 不会报告失败,只会返回null。
public class ArticleManagerTest {
@Mock private ArticleCalculator calculator;
@Mock(name = "database") private ArticleDatabase dbMock; // note the mock name attribute
@InjectMocks private ArticleManager manager;
@Test public void shouldDoSomething() {
manager.initiateArticle();
verify(database).addListener(any(ArticleListener.class));
}
}
3.创建预返回的存根
保存存根,当提取对应的值时,如果有存根则会返回存根
thenReturn:返回值
thenThrow:抛出异常
LinkedList mockedList = mock(LinkedList.class);
//保存存根
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
System.out.println(mockedList.get(0));
System.out.println(mockedList.get(1));//抛出异常
System.out.println(mockedList.get(999));
//虽然可以验证存根调用,但通常只是多余的
verify(mockedList).get(0);
- 没有存根时,mock将视情况返回null,原始/原始包装器值或空集合。例如,对于int / Integer为0,对于boolean / Boolean为false。
- 存根可以被覆盖:例如,通用存根可以进入夹具设置,但是测试方法可以覆盖它。请注意,过多的存根是潜在的错误
- 一旦存根,该方法将始终返回存根值,而不管其被调用了多少次。
mockito还提供迭代器存根和回调存根,不常用,感兴趣可以看官方文档
4. 参数模拟器
如果使用参数匹配器,则所有参数都必须由匹配器提供。例如:(示例显示了验证,但对存根也是如此):
static <T> T |
**[any](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#any())**() 匹配任何内容,包括null和varargs。 |
---|---|
static <T> T |
**[any](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#any(java.lang.Class))**([Class](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html?is-external=true)<T> type) 匹配任何给定类型的对象,不包括null。 |
static boolean |
**[anyBoolean](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyBoolean())**() 任何boolean 或非空 Boolean |
static byte |
**[anyByte](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyByte())**() 任何byte 或者非空 Byte 。 |
static char |
**[anyChar](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyChar())**() 任何char 或者非空 Character 。 |
static <T> [Collection](https://docs.oracle.com/javase/6/docs/api/java/util/Collection.html?is-external=true)<T> |
**[anyCollection](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyCollection())**() 任何非null Collection 。 |
static <T> [Collection](https://docs.oracle.com/javase/6/docs/api/java/util/Collection.html?is-external=true)<T> |
**[anyCollectionOf](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyCollectionOf(java.lang.Class))**([Class](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html?is-external=true)<T> clazz) 不推荐使用。 对于Java 8,此方法将在Mockito 3.0中删除。此方法仅用于通用友好性以避免转换,Java 8中不再需要此方法。 |
static double |
**[anyDouble](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyDouble())**() 任何double 或者非空 Double 。 |
static float |
**[anyFloat](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyFloat())**() 任何float 或者非空 Float 。 |
static int |
**[anyInt](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyInt())**() 任何int或non-null Integer 。 |
static <T> [Iterable](https://docs.oracle.com/javase/6/docs/api/java/lang/Iterable.html?is-external=true)<T> |
**[anyIterable](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyIterable())**() 任何非null Iterable 。 |
static <T> [Iterable](https://docs.oracle.com/javase/6/docs/api/java/lang/Iterable.html?is-external=true)<T> |
**[anyIterableOf](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyIterableOf(java.lang.Class))**([Class](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html?is-external=true)<T> clazz) 不推荐使用。 对于Java 8,此方法将在Mockito 3.0中删除。此方法仅用于通用友好性以避免转换,Java 8中不再需要此方法。 |
static <T> [List](https://docs.oracle.com/javase/6/docs/api/java/util/List.html?is-external=true)<T> |
**[anyList](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyList())**() 任何非null List 。 |
static <T> [List](https://docs.oracle.com/javase/6/docs/api/java/util/List.html?is-external=true)<T> |
**[anyListOf](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyListOf(java.lang.Class))**([Class](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html?is-external=true)<T> clazz) 不推荐使用。 对于Java 8,此方法将在Mockito 3.0中删除。此方法仅用于通用友好性以避免转换,Java 8中不再需要此方法。 |
static long |
**[anyLong](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyLong())**() 任何long 或者非空 Long 。 |
static <K,V> [Map](https://docs.oracle.com/javase/6/docs/api/java/util/Map.html?is-external=true)<K,V> |
**[anyMap](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyMap())**() 任何非null Map 。 |
static <K,V> [Map](https://docs.oracle.com/javase/6/docs/api/java/util/Map.html?is-external=true)<K,V> |
**[anyMapOf](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyMapOf(java.lang.Class,%20java.lang.Class))**([Class](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html?is-external=true)<K> keyClazz, [Class](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html?is-external=true)<V> valueClazz) 不推荐使用。 对于Java 8,此方法将在Mockito 3.0中删除。此方法仅用于通用友好性以避免转换,Java 8中不再需要此方法。 |
static <T> T |
**[anyObject](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyObject())**() 不推荐使用。 这将在Mockito 3.0中删除(仅适用于Java 8) |
static <T> [Set](https://docs.oracle.com/javase/6/docs/api/java/util/Set.html?is-external=true)<T> |
**[anySet](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anySet())**() 任何非null Set 。 |
static <T> [Set](https://docs.oracle.com/javase/6/docs/api/java/util/Set.html?is-external=true)<T> |
**[anySetOf](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anySetOf(java.lang.Class))**([Class](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html?is-external=true)<T> clazz) 不推荐使用。 对于Java 8,此方法将在Mockito 3.0中删除。此方法仅用于通用友好性以避免转换,Java 8中不再需要此方法。 |
static short |
**[anyShort](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyShort())**() 任何short 或者非空 Short 。 |
static [String](https://docs.oracle.com/javase/6/docs/api/java/lang/String.html?is-external=true) |
**[anyString](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyString())**() 任何非空 String |
static <T> T |
**[anyVararg](https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/ArgumentMatchers.html#anyVararg())**() 不推荐使用。 从2.1.0开始使用 any() ) |
5. 验证调用的次数
mock过的类,可以获取被调用的数据
//using mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
//调用一次
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
//调用2次,三次
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
//没调用过
verify(mockedList, never()).add("never happened");
//最少和最多调用次数
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");
6. 参数校验
Junit4本身就提供了assert方法,详见Junit4中的新断言assertThat的使用方法,无需学习Mockito中的校验
Mockito除了提供校验外,还可以跟踪mock对象等,进行更加细致的操作,如有需要可以查看官方文档
**