模拟测试框架之Mockito使用及原理分析
作者:互联网
前言
当我们进行单元测试时,可能某个依赖的服务还没有开发完成(如RPC或HTTP调用),这种情况下我们就可以对依赖服务创建一个模拟对象,这样我们就可以更加关注于当前的测试类,而不是依赖的服务类。Mockito是一个强大的模拟测试框架,可以让我们很方便的创建模拟对象并进行行为验证。
添加maven依赖
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.3.3</version>
</dependency>
创建模拟对象
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.mockito.Mockito;
public class TestMock2 {
public static void main(String[] args) {
List<String> mockList = Mockito.mock(List.class);
System.out.println(mockList.getClass());
System.out.println(Arrays.toString(mockList.getClass().getInterfaces()));
mockList = Mockito.mock(ArrayList.class);
System.out.println(mockList.getClass());
System.out.println(Arrays.toString(mockList.getClass().getInterfaces()));
}
}
既可以对接口创建模拟对象,也可以对具体的实现类创建模拟对象,Mockito底层使用ByteBuddy库来创建代理类,使用Objenesis库来实例化对象。
ByteBuddy是一个代码生成和操作的类库,类似于Cglib、javassist,底层也是ASM库,官网。
Objenesis是一个小的java库,可以让我们绕过构造器来实例化对象,Spring通过Cglib创建代理对象的过程中就使用到了Objenesis,
更多信息可以查看java中Objenesis库简单使用。
创建部分模拟对象
import java.util.ArrayList;
import java.util.List;
import org.mockito.Mockito;
public class TestMock4 {
public static void main(String[] args) {
List<String> mockList = Mockito.mock(ArrayList.class);
List<String> spyList = Mockito.spy(ArrayList.class);
mockList.add("hello");
spyList.add("hello");
System.out.println(mockList.get(0));//null
System.out.println(spyList.get(0));//hello
}
}
spy()方法和mock()的区别在于
- mock()方法在没有找到对应的配置行为时,返回默认结果,如int类型返回0
- spy()方法在没有找到对应的配置行为时,委托给被代理对象处理,这里就是ArrayList。如果被代理对象为接口,也是返回默认结果。
具体可以查看CallsRealMethods类实现。
配置方法行为
import java.util.List;
import org.mockito.Mockito;
public class TestMock2 {
public static void main(String[] args) {
List<String> mockList = Mockito.mock(List.class);
Mockito.when(mockList.size()).thenReturn(1);
System.out.println(mockList.size());
Mockito.when(mockList.get(0)).thenReturn("hello");
System.out.println(mockList.get(0));
//抛出异常
Mockito.when(mockList.get(0)).thenThrow(new RuntimeException("error"));
System.out.println(mockList.get(0));
}
}
为List.size()行为配置一个结果1,下次调用此方法时直接返回此结果。
没有配置行为时,使用默认结果,配置在DefaultMockitoConfiguration的ReturnsEmptyValues类中,如对int值返回0,对Iterable类型,返回一个空的ArrayList。
接下来分析一下原理:
- 当我们调用List.size()时,Mockito会拦截此方法,将方法调用(List.size())的详细信息保存到模拟对象的上下文中
- 调用Mockito.when()方法时,会从上下文(具体为MockingProgressImpl)中获取到最后一次方法调用(List.size())信息
- 将thenReturn()的参数作为结果保存起来,下次调用时直接获取。
方法调用容器为InvocationContainerImpl,其中包含一系列StubbedInvocationMatcher对象,每一个对象都是一个方法调用和具体结果的封装。
验证方法调用次数
import java.util.List;
import org.mockito.Mockito;
public class TestMock3 {
public static void main(String[] args) {
List<String> mockList = Mockito.mock(List.class);
mockList.size();
mockList.size();
Mockito.verify(mockList, Mockito.times(2)).size();
}
}
如果方法没有被调用2次,就会抛出异常。
总结
Mockito基本原理就是对接口或具体类创建动态代理对象,在实际进行方法调用时,会查找是否已经配置了结果,没有就使用默认结果。
参考
Mockito官网
一文让你快速上手 Mockito 单元测试框架
手把手教你 Mockito 的使用
标签:mockList,框架,Mockito,List,println,import,class,模拟 来源: https://www.cnblogs.com/strongmore/p/16271972.html