赞
踩
要进行测试的方法存在外部依赖(如数据库,Redis,第三方接口调用等),为了能够专注对该方法或者单元的逻辑进行测试,就希望能够虚拟出外部依赖,避免外部依赖成为测试的阻塞项。
Mock类框架:用于Mock外部依赖。
官网:http://mockito.org/
官网文档:https://www.javadoc.io/doc/org.mockito/mockito-core/4.5.1/org/mockito/Mockito.html#13
限制:老版本对于final class、final method、statis method、private method均不能对Mockito mock,目前新版本已经支持final class、final method、statis method方法的mock,具体可以参考官网(有空了再补)
文档:https://github.com/powermock/powermock/wiki/Getting-Started
PowerMock
是一款功能十分强大的Mock工具,其基本语法与Mockito
兼容,同时扩展了许多Mockito
缺失的功能,包括对支持对私有、静态和构造方法实施Mock。但由于使用了自定义类加载器,会导致Jacoco在默认的on-the-fly
模式下覆盖率跌零。
powerMock是基于easyMock或Mockito扩展出来的增强版本,所以powerMock分两种类型,如果你习惯于使用easyMock的,那你就下载基于easyMock的powerMock,反之你喜欢用mockito的话就下载另一种PowerMock。
但是好像也没有多少人用。。。
文档:https://alibaba.github.io/testable-mock/#/
TestableMock
现在已不仅是一款轻量易上手的单元测试Mock工具,更是以简化Java单元测试为目标的综合辅助工具集,与PowerMock
基本平齐,且极易上手,只需掌握一个@MockInvoke
注解就可以完成绝大多数Mock操作。
工具 | 原理 | 最小Mock单元 | 对被Mock方法的限制 | 上手难度 | IDE支持 |
---|---|---|---|---|---|
Mockito | 动态代理 | 类 | 不能Mock私有方法 | 较容易 | 很好 |
PowerMock | 自定义类加载器 | 类 | 任何方法皆可 | 较复杂 | 较好 |
JMockit | 运行时字节码修改 | 类 | 不能Mock构造方法(new操作符) | 较复杂 | 一般 |
TestableMock | 运行时字节码修改 | 方法 | 任何方法皆可 | 较容易 | 较好 |
导入Mockito坐标和junit5的坐标
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.6.28</version>
<scope>compile</scope>
</dependency>
导入Mockito坐标和junit5的坐标,前期工作已经完成。
如果在springboot中我们还可以直接引用下面的坐标即可,其中的依赖已经包含了上述两者。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.0.13</version>
<scope>test</scope>
</dependency>
方法类型 | 方法插桩 | 方法不插桩 | 作用对象 | 最佳实践 |
---|---|---|---|---|
mock对象 | 执行插桩逻辑 | 返回mock对象的默认值 | 类、接口 | 被测试类或者其他依赖 |
spy对象 | 执行插桩逻辑 | 调用真实方法 | 类、接口 | 被测试类 |
测试版本 | 方法一 | 方法二 | 方法三 |
---|---|---|---|
junit4 | @RunWith(MockitoJUnitRunner.class) + @Mock等注解 | MockitoAnnotations.initMocks(this); | Mockito.mock(x.class) |
junit5 | @ExtendWith(MockitoExtension.class)+ @Mock等注解 | MockitoAnnotations.initMocks(this); | Mockito.mock(x.class) |
MockitoAnnotations.initMocks(this)方法已经被openMocks(this)替代。
我们现在来介绍一下初始化的三种方式:现阶段先关注初始化方法就行。。
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.junit.jupiter.api.BeforeEach; import org.mockito.MockitoAnnotations; import org.mockito.Mockito; /** * 初始化mock/spy对象的第一种方式 */ @ExtendWith(MockitoExtension.class) public class TeacherServiceTestMethod1 { @Mock private TeacherService teacherService; @Spy private UserService userService; @Test public void test1(){ //Mockito.mockingDetails(teacherService).isMock() 用来判断该对象是不是一个mock的对象 System.out.println(Mockito.mockingDetails(teacherService).isMock()); System.out.println(Mockito.mockingDetails(userService).isSpy()); System.out.println(); } } /** * 初始化mock/spy对象的第二种方式 */ class TeacherServiceTestMethod2 { @Mock private TeacherService teacherService; @Spy private UserService userService; @BeforeEach void setUp() { //MockitoAnnotations.initMocks(this); 该方法已过时 MockitoAnnotations.openMocks(this); } @Test public void test1(){ System.out.println(Mockito.mockingDetails(teacherService).isMock()); System.out.println(Mockito.mockingDetails(userService).isSpy()); System.out.println(); } } /** * 初始化mock/spy对象的第三种方式 */ class TeacherServiceTestMethod3 { private TeacherService teacherService; private UserService userService; @BeforeEach void setUp() { userService = Mockito.mock(UserService.class); teacherService = Mockito.spy(TeacherService.class); } @Test public void test1(){ System.out.println(Mockito.mockingDetails(teacherService).isMock()); System.out.println(Mockito.mockingDetails(userService).isSpy()); System.out.println(); } }
我们随意找一个Test方法debug一下只要看到对象是这样就ok了。注意$MockitoMock
就是说明模拟成功了
com.surpass.service.UserService$MockitoMock$1070386111@55e8ec2f
将参数匹配和方法插桩一起示例。。。
指定调用某个方法时的行为(stubbing),达到相互隔离的目的。
import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; /** * 参数匹配:通过方法签名(参数)来指定哪些方法调用时需要被处理(插桩、verify验证) * 注意:在只用匹配器是要么都用要么都不用,禁止混搭!!举例 * 正确:getUserListByTeacher("name", "address")或者getUserListByTeacher(anyString(), anyString()) * 错误:getUserListByTeacher(anyString(), "address") */ @ExtendWith(MockitoExtension.class) public class ParamMatcherTest { @Mock private TeacherService teacherService; /** * 对于mock对象不会调用真实方法,直接返回mock对象的默认值 * 默认值(int)、null、空集合 */ @Test public void test1(){ TeacherEntity teacher = teacherService.getTeacherByName(""); System.out.println("teacher = " + teacher); Set<UserEntity> userListByTeacherName = teacherService.getUserListByTeacherName(""); System.out.println("userListByTeacherName = " + userListByTeacherName); } /** * 方法插桩时的参数匹配 * 参数匹配时类(ArgumentMatchers)是匹配参数的主要成员,例如: * any()则表示某一个方法传入任何类型都符合要求 * anyString()则表示字符串类型都符合要求 * !!!注意:所有的匹配都不包括null值 */ @Test public void test2(){ TeacherEntity teacherEntity = new TeacherEntity(); teacherEntity.setName("张三"); teacherEntity.setAddress("北京"); //when(teacherService.getTeacherByName("")).thenReturn(teacherEntity); 此行插桩方式也可,此插桩意为当执行getTeacherByName方法是会返回之前创建好的对象teacherEntity doReturn(teacherEntity).when(teacherService).getTeacherByName(any()); TeacherEntity teacher = teacherService.getTeacherByName("123"); System.out.println("teacher = " + teacher); //验证校验teacherService.getTeacherByName()调用的次数,之前调了一次所以校验通过,如两次则抛异常 verify(teacherService, times(1)).getTeacherByName(any()); } } 控制台将打印如下内容 teacher = TeacherEntity(id=null, name=张三, address=北京, age=0)
其他项目的解释
@Mock private List<String> mockList; /** * 被插桩的方法在调用时不会执行实际的逻辑,直接返回指定的返回值 */ @Test public void test3(){ /** * 指定返回值 */ //方法插桩:当调用mockList.get(0)时返回指定返回值“zero” doReturn("zero").when(mockList).get(0); Assertions.assertEquals("zero", mockList.get(0)); when(mockList.get(1)).thenReturn("one"); Assertions.assertEquals("one", mockList.get(1)); /** * void返回值方法插桩 */ doNothing().when(mockList).clear(); mockList.clear(); verify(mockList, times(1)).clear(); /** * 抛异常 */ doThrow(RuntimeException.class).when(mockList).clear(); try { mockList.clear(); //断言证明插桩失败 Assertions.fail(); } catch (Exception e) { Assertions.assertTrue(e instanceof RuntimeException); } //或 when(mockList.get(anyInt())).thenThrow(RuntimeException.class); try { mockList.get(4); //断言证明插桩失败 Assertions.fail(); } catch (Exception e) { Assertions.assertTrue(e instanceof RuntimeException); } /** * 多次插桩 * 意为第一次调用返回1,第二次调用返回2,第三次以及以后调用返回3 */ when(mockList.size()).thenReturn(1).thenReturn(2).thenReturn(3); //或-----两者相同 when(mockList.size()).thenReturn(1, 2, 3); Assertions.assertEquals(1, mockList.size()); Assertions.assertEquals(2, mockList.size()); Assertions.assertEquals(3, mockList.size()); Assertions.assertEquals(3, mockList.size()); } /** * thenAnswer来实现对指定逻辑的插桩 */ @Test public void test4() { when(mockList.get(anyInt())).thenAnswer((Answer<String>) invocation -> { //getArgument表示获取插桩方法(此处为mockList.get(anyInt()))的第几个参数值 Integer argument = invocation.getArgument(0, Integer.class); return String.valueOf(argument * 100); }); //执行get方法 System.out.println(mockList.get(1)); } /** * 调用真实逻辑:控制台打印 * 进入方法: getTeacher * TeacherEntity(id=1, name=123, address=北京市, age=-1166257546) */ @Test public void test5() { when(teacherService.getTeacher(any())).thenCallRealMethod(); System.out.println(teacherService.getTeacher("123")); }
TeacherService实现 toString()
方法,调用 System.out.println(teacherService);
控制台打印如下内容:
TeacherService{teacherDao=teacherDao, userDao=userDao}
dubug能够看到两个Dao被自动注入到Service中去。
@ExtendWith(MockitoExtension.class) public class InjectMocksTest { /** * 被InjectMocks注解标注的助兴必须是实现类,因为mockito会创建对应的实例对象 * 未经过mockito处理的普通对象会配合@spy注解使其变成默认调用真实方法的mock对象 * mockito会使用spy对象或mock对象注入到InjectMocks对应的实例对象中 */ @Spy @InjectMocks private TeacherService teacherService; @Mock private TeacherDao teacherDao; @Mock private UserDao userDao; @Test public void test1(){ System.out.println(teacherService); } }
junit5常用断言类库:org.junit.jupiter.api.Assertions
。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。