mockito介绍
mock作用
在单元测试中,对于一个类中的方法,常常需要依赖其他类的方法、操作数据dto等对象实例。
-
方法mock:依赖的方法又可能依赖其他方法,呈现级联的树状结构。
- 问题:在一些情况下,这个依赖树会很深,其中依赖的一个子方法一旦修改出现问题,如果引起大量的单测不可用,对于问题的定位而言十分困难,而且也失去了单元测试的最小单元化、只测试当前逻辑功能的意义。因此在单元测试中,对依赖的子方法逻辑功能的隔离是十分重要的;
- mock解决方案:通过对操作对象dto进行mock、调用子方法时进行代理跳过具体的执行逻辑直接对执行结果进行模拟,实现对子方法和当前参数处理的模拟运行。
-
操作数据mock:执行逻辑对于不同case的处理数据,可能有不同的分支逻辑进行处理;
- 问题:如果不能覆盖所有分支,对于上线后千奇百怪的实际用户操作产生的数据,存在踩坑、不可用的风险。
- mock解决方案:理由mock构造不同的数据,作为入参、方法调用结果进行各分支的测试。
因此在单元测试中mock技术是其核心功能。
mockito竞品
EasyMock(https://easymock.org/)、
JMock(http://jmock.org/)、
PowerMock(https://github.com/powermock/powermock)
mockito使用
引入依赖
引入mockito-core依赖
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.7.19</version>
<scope>test</scope>
</dependency>
mockito只关注mock,还需要依赖junit进行单测。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
在单测类上注入使用mockito
以下是一个springboot的mock例子,使用@MockBean进行bean的mock和注入,在处理逻辑中利用Mockito.when(xxx).thenReturn(xxx)对调用方法进行mock,实现内部依赖方法调用的mock解耦,实现单元测试只关注当前待测试方法的目标。
@RunWith(SpringRunner.class)
@SpringBootTest
publicclass UserServiceTest {
@Autowired
private UserService userService;
/* mock生成代理 */
@MockBean
private UserDao userDao;
@Test
public void getUserById() throws Exception {
// 定义当调用mock userDao的getUserById()方法,并且参数为3时,就返回id为200、name为I'm mock3的user对象
Mockito.when(userDao.getUserById(3)).thenReturn(new User(200, "I'm mock 3"));
// 返回的会是名字为I'm mock 3的user对象
User user = userService.getUserById(1);
Assert.assertNotNull(user);
Assert.assertEquals(user.getId(), new Integer(200));
Assert.assertEquals(user.getName(), "I'm mock 3");
}
}
使用doAnswer实现mock调用结果
public class MockitoAnswerTest {
@InjectMocks
private TestClass testClass;
@Mock
private CalledClass calledClasee;
@Test
public void analysisTest() {
MockitoAnnotations.openMocks(this);
Mockito.doAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
return "mocked answer";
}
}).when(calledClasee).call(Mockito.any());
String result =
testClass.test(new Object());
Assert.assertEquals("mocked answer", result);
}
Mockito 的限制
上述就是 Mockito 的 mock 对象使用方法,不过当使用 Mockito 在 mock 对象时,有一些限制需要遵守
1. 不能 mock 静态方法
2. 不能 mock private 方法
3. 不能 mock final class
mockito实现-源码分析
加载初始化mockito
在单元测试的启动阶段开启mockito注解,传递当前测试实例。
@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
}
openMock源码
openMocks方法会对当前单元测试类中的各字段field,通过
/**
* Initializes objects annotated with Mockito annotations for given testClass:
* @{@link org.mockito.Mock}, @{@link Spy}, @{@link Captor}, @{@link InjectMocks}
* <p>
* See examples in javadoc for {@link MockitoAnnotations} class.
*
* @return A closable to close when completing any tests in {@code testClass}.
*/
public static AutoCloseable openMocks(Object testClass) {
if (testClass == null) {
throw new MockitoException(
"testClass cannot be null. For info how to use @Mock annotations see examples in javadoc for MockitoAnnotations class");
}
AnnotationEngine annotationEngine =
new GlobalConfiguration().tryGetPluginAnnotationEngine();
return annotationEngine.process(testClass.getClass(), testClass);
}
AnnotationEngine有3个实现类,IndependentAnnotationEngine(处理)、SpyAnnotationEngine(处理@Spy注解)、InjectingAnnotationEngine(组装代理IndependentAnnotationEngine和SpyAnnotationEngine)
ScopedMock用来管理mock对象的生命周期,mock对象在threadLocal中维护,当单测退出时,清空当前线程(单测一般是单线程的)threadLocal中的mock出来的对象进行回收处理。具体逻辑查看org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker类。
@Mock标注字段的对象代理
@Mock标注的单元测试对象中filed对象的创建是由上面的openMock中对@Mock注解处理时createMockFor(annotation,field)触发的。
实现逻辑如下。
org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker中newInstance()创建对象实例方法如下。
调用构造函数实例化mock对象时,参数列表如果是基础类型则传0(false)、对象类型传null
最终用constructor.newInstance(arguments)反射等方式将对象创建出来。
参考资料
- 万字长文:一文详解单元测试干了什么 https://mp.weixin.qq.com/s/9_TQbVSl1CQLQzuUrsrHLQ
- Java单元测试Mock框架Mockito入门介绍 https://cloud.tencent.com/developer/article/1850562
- Mock工具之Mockito实战 https://zhuanlan.zhihu.com/p/580113391