Mockito使用简介
Mock基础知识
什么是Mock
Mock就是在测试或者开发过程中,对不容易创造或者获取的对象,创造Mock对象来模拟对象的行为。Mock 最大的功能是帮你把单元测试进行解耦,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
Mock的目的
- 模拟对象方法行为,返回特定值
- 验证对象某些方法的调用情况
Mock的应用场景
-
团队并行开发 有了Mock,前、后端人员只需要定义好接口文档就可以开始并行工作,互不影响。后端与后端之间如果有接口耦合,也同样使用Mock解决。 -
开启TDD模式 TDD,即测试驱动开发。单元测试是TDD实现的基石,而TDD经常会碰到协同模块尚未开发完成的情况,但是有了mock,这些一切都不是问题。当接口定义好后,测试人员就可以创建一个Mock,把接口添加到自动化测试环境,提前创建测试。 -
隔离系统 假如需要调用一个post请求,为了获得响应,来看当前系统是否能正确处理返回的“响应”,但是这个post请求会造成数据库中数据的污染,那么就可以充分利用Mock,构造一个虚拟的post请求,指定返回的数据就OK。 -
模拟访问资源 比如说,你需要调用一个“墙”外的资源来方便自己调试,就可以自己Mock一个。 -
测试场景 接口测试:对接口进行测试,排除接口对外部环境的依赖,避免外部环境问题而导致的测试失败。 功能测试:如果数据准备比较麻烦或者造数据要双方配合成本比较高的项目,在对外部接口进行第一次全面联调测试后,仍然可以考虑使用mock来全面测试自己内部的功能逻辑。 UI自动化测试:对数据源进行mock,UI自动化测试时,页面上展示的信息是来源于其他的数据源。 测试覆盖度:例如一个接口有许多种返回类型,有些返回类型不是很容易发生,例如服务器崩溃返回500。这个时候就可以通过mock来模拟返回,达到完整覆盖测试用例的目的。
Mock框架比较
名称 | 简介 | 说明 |
---|
Mockito | 消除了对期望行为(expectations)的需要。其它的mocking库需要你在执行前记录期望行为(expectations),而这导致了丑陋的初始化代码。 | 相对 EasyMock 学习成本低,而且具有非常简洁的API,测试代码的可读性很高。 | PowerMock | 这个工具是在EasyMock和Mockito上扩展出来的,目的是为了解决EasyMock和Mockito不能解决的问题,比如对 static, final, private 方法均不能mock。 | PowerMock 在扩展功能时完全采用和被扩展的框架相同的 API, 熟悉 PowerMock 所支持的模拟框架的开发者会发现 PowerMock 非常容易上手。PowerMock 的目的就是在当前已经被大家所熟悉的接口上通过添加极少的方法和注释来实现额外的功能。 | MockMVC | 基于RESTful风格的SpringMVC单元测试,可以测试完整的SpringMVC流程,即从URL请求到控制处理器,到视图渲染都可以测试。 | |
Mockito使用介绍
环境依赖
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.2</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.9.0</version>
</dependency>
常用注解
-
@ExtendWith(MockitoExtension.class) ExtendWith 是 JUnit 5 的注解,该注解可以使用在类或者方法上。表示为使用该注解的类或方法注册扩展功能。@ExtendWith(MockitoExtension.class) 表示为测试类注册 Mockit 测试扩展功能。 JUnit5 Extensions 详细介绍 -
@InjectMocks 使用该注解的类,会实例化一个对象,使用该对象会调用其真实方法。其他使用 @Mock 或 @Spy注解的类会别注入到该实例中。 -
@Mock 使用该注解会 mock 一个对象,对该对象的调用不会执行真实方法。 -
@Spy 使用该注解会 mock 一个对象,对该对象的调用会执行真实代码,区别于 @Mock。 @InjectMocks 和 @Spy 可以组合进行使用,表示实例化对象,并且可以对该对象方法设置stub。@InjectMocks 和 @Mock 不能组合使用,否则会报错。使用 @InjectMocks 进行对象装配时,需要在类上使用 @ExtendWith(MockitoExtension.class) 注解。或者调用MockitoAnnotations.openMocks(this) 手动进行装配。
常用方法
- Mockito.mock() mock 对象,对该对象调用 不会 执行真实方法
@Test
public void demoTest5() {
List list = Mockito.mock(List.class);
list.add("first");
Assertions.assertNull(list.get(0));
}
- Mockito.spy() mock 对象,对该对象的调用方法 会 执行真实方法
@Test
public void demoTest6() {
ArrayList list = Mockito.spy(ArrayList.class);
list.add("first");
Assertions.assertEquals(list.get(0), "first");
}
- Mockito.verify() 验证方法调用行为,是否调用,确切调用次数,至少调用的次数
@Test
public void demoTest() {
List list = Mockito.mock(List.class);
list.add(1);
list.add(2);
list.add(2);
list.add(3);
list.add(3);
list.add(3);
Mockito.verify(list).add(1);
Mockito.verify(list, Mockito.times(2)).add(2);
Mockito.verify(list, Mockito.times(3)).add(3);
Mockito.verify(list, Mockito.never()).add(0);
Mockito.verify(list, Mockito.atMostOnce()).add(1);
Mockito.verify(list, Mockito.atLeast(2)).add(2);
Mockito.verify(list, Mockito.atMost(4)).add(3);
}
- Mockito.when().thenRuturn() 为测试代码生成 stub
@Test
public void demoTest1() {
List list = Mockito.mock(List.class);
Mockito.when(list.get(0)).thenReturn("first");
Mockito.when(list.get(1)).thenThrow(new RuntimeException("exception"));
Assertions.assertEquals(list.get(0), "first");
Assertions.assertEquals(Assertions.assertThrows(
RuntimeException.class, () -> list.get(1)).getMessage(), "exception");
Assertions.assertEquals(list.get(2), null);
}
@Test
public void demoTest3() {
List list = Mockito.mock(List.class);
Mockito.when(list.get(0)).thenReturn("first");
Mockito.when(list.get(0)).thenReturn("second");
Mockito.when(list.get(0)).thenReturn("third");
Assertions.assertEquals(list.get(0), "third");
Assertions.assertEquals(list.get(0), "third");
Assertions.assertEquals(list.get(0), "third");
Mockito.reset(list);
Mockito.when(list.get(0)).thenReturn("first").thenReturn("second").thenReturn("third");
Assertions.assertEquals(list.get(0), "first");
Assertions.assertEquals(list.get(0), "second");
Assertions.assertEquals(list.get(0), "third");
}
- Mockito.reset() 重置与mock对象的交互行为和设置的 stub
@Test
public void demoTest2() {
List list = Mockito.mock(List.class);
Mockito.when(list.get(0)).thenReturn("first");
Assertions.assertEquals(list.get(0), "first");
Mockito.reset(list);
Assertions.assertEquals(list.get(0), null);
}
- thenCallRealMethod() 或 doCallRealMethod() 调用真实方法
@Test
public void demoTest4() {
Student student = Mockito.mock(Student.class);
Assertions.assertEquals(student.studentName(), null);
Mockito.reset(student);
Mockito.when(student.studentName()).thenCallRealMethod();
Assertions.assertEquals(student.studentName(), "zhangsan");
}
常见问题及解决方法
可以根据需要mock方法的多少,选择使用 Mock() 或 Spy()
public class MockitoTestDemo {
public void goHome() {
doSomeThingA();
doSomeThingB();
}
public void doSomeThingB() {
System.out.println("good day");
}
public void doSomeThingA() {
System.out.println("you should not see this message.");
doSomeThingB();
}
public boolean go() {
System.out.println("I say go go go!!");
return true;
}
@Test
public void callRealMethodTest() {
MockitoTestDemo testDemo = Mockito.mock(MockitoTestDemo.class);
Mockito.doCallRealMethod().when(testDemo).goHome();
Mockito.doCallRealMethod().when(testDemo).doSomeThingB();
testDemo.goHome();
Mockito.verify(testDemo, Mockito.times(1)).doSomeThingA();
Mockito.verify(testDemo, Mockito.times(1)).doSomeThingB();
}
@Test
public void spyTest() {
MockitoTestDemo testDemo = Mockito.spy(new MockitoTestDemo());
Mockito.when(testDemo.go()).thenReturn(false);
Assertions.assertFalse(testDemo.go());
Mockito.doReturn(false).when(testDemo).go();
Assertions.assertFalse(testDemo.go());
}
}
-
’UnnecessaryStubbingException’ 异常 org.mockito.exceptions.misusing.UnnecessaryStubbingException: Unnecessary stubbings detected. Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class. 出现以上错误提示的时候,说明出现了未被使用的 stub. Mockito使用 Strict stubbing 特性保证代码的整洁,但是有可能会带来以上错误,这个时候可以去掉这些未被使用的stub。或者使用以下方法: Mockito.lenient().when(mock.foo()).thenReturn("ok"); 或者 Foo mock = Mockito.mock(Foo.class, withSettings().lenient()); -
与 MybatisPlus 整合问题 测试过程中如果遇到 MybatisPlus 提供的, 例如 saveOrUpdate() 、updateBatchById() 等方法,但是无法跳过的时候。可以使用 @Spy 或者 Mockito.spy() 进行mock 类,并使用 Mockito.doReturn(false).when(service).saveOrUpdate() 生成测试stub。 -
出现 ‘MybatisPlusException: can not find lambda cache for this entity’ 错误提示 使用 MybatisPlus 提供的 LambdaWrapper 的时候会使用到Entity的一些缓存起来的类相关信息,找不到便会报错。 TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), PrdNoticeMachineEntity.class); ,可以手动通过添加以上代码解决该问题。
参考文档
|