Mock技术文档
Mock技术文档
一、Mock简介
1.1 什么是Mock
Mock(模拟)是一种测试技术,用于创建虚拟对象来模拟真实对象的行为。在单元测试中,Mock对象可以替代真实依赖,使测试更加独立、可控和快速。
1.2 Mock的核心概念
- Mock对象:模拟真实对象的虚拟对象,可以预设其行为
- Stub(桩):预定义返回值的Mock对象
- Spy(间谍):部分Mock,保留真实对象的部分行为
- Verify(验证):验证Mock对象的方法是否被调用
1.3 Mock的优势
- 隔离性:测试不依赖外部系统(数据库、网络、第三方服务)
- 可控性:可以模拟各种场景(正常、异常、边界情况)
- 快速性:避免真实IO操作,测试执行更快
- 稳定性:不受外部环境变化影响
- 可重复性:测试结果可重复,便于CI/CD集成
二、应用场景
2.1 单元测试场景
2.1.1 依赖外部服务
- 第三方API调用:支付接口、短信服务、地图服务等
- 微服务调用:其他服务的RPC/HTTP调用
- 消息队列:Kafka、RocketMQ等消息中间件
2.1.2 依赖数据库
- 数据持久化:MyBatis、JPA等ORM框架
- 缓存操作:Redis、Memcached等
- 文件系统:文件读写操作
2.1.3 依赖系统资源
- 时间相关:
System.currentTimeMillis()、new Date() - 随机数:
Random、UUID - 环境变量:系统配置、环境参数
2.2 集成测试场景
- 服务降级测试:模拟下游服务不可用
- 性能测试:模拟高并发场景
- 异常场景测试:模拟网络超时、服务异常
2.3 开发阶段场景
- 并行开发:依赖模块未完成时,使用Mock进行开发
- 接口联调:前端开发时Mock后端接口
- 演示环境:使用Mock数据展示功能
三、在实际项目中的应用
3.1 项目中的Mock应用示例
基于当前项目(Spring Boot + MyBatis Plus),以下是常见的Mock应用场景:
3.1.1 Mock第三方服务调用
@RunWith(SpringRunner.class)
@SpringBootTest
public class AbilityPlatformServiceTest {@MockBeanprivate AbilityPlatformService abilityPlatformService;@Autowiredprivate YourService yourService;@Testpublic void testServiceWithMock() {// Mock第三方服务返回HujiAndMemberVo mockVo = new HujiAndMemberVo();mockVo.setName("测试");ResultVO<List<HujiAndMemberVo>> mockResult = ResultVO.success(Arrays.asList(mockVo));when(abilityPlatformService.getHujiAndMember(anyString(), anyString())).thenReturn(mockResult);// 执行测试YourResult result = yourService.doSomething("340121198409123439", "张雨");// 验证结果assertNotNull(result);verify(abilityPlatformService, times(1)).getHujiAndMember(anyString(), anyString());}
}
3.1.2 Mock数据库操作
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {@MockBeanprivate UserBaseMapper userBaseMapper;@Autowiredprivate UserService userService;@Testpublic void testFindUser() {// Mock数据库查询结果UserBase mockUser = new UserBase();mockUser.setId(1L);mockUser.setName("测试用户");when(userBaseMapper.selectById(1L)).thenReturn(mockUser);// 执行测试UserBase user = userService.getUserById(1L);// 验证assertNotNull(user);assertEquals("测试用户", user.getName());}
}
3.1.3 Mock大数据接口(项目实际案例)
项目中已有 BmBigdataMock实体用于Mock大数据接口:
// 实际项目中的Mock应用
private Residence getMockResidence(String childIdCard, String parentIdCard) {LambdaQueryWrapper<BmBigdataMock> wrapper = new LambdaQueryWrapper<>();wrapper.eq(BmBigdataMock::getConfigKey, "getResidence");wrapper.eq(BmBigdataMock::getChildId, childIdCard);wrapper.eq(BmBigdataMock::getFatherId, parentIdCard);List<BmBigdataMock> list = bmBigdataMockMapper.selectList(wrapper);if (CollectionUtil.isEmpty(list)) {throw new BigDataRunTimeException("大数据接口异常");}String config = list.get(0).getConfigValue();Residence residence = JSONObject.parseObject(config, Residence.class);// 处理业务逻辑...return residence;
}
应用场景:
- 开发环境无法连接真实大数据服务
- 测试环境需要可控的测试数据
- 避免频繁调用真实接口产生费用
3.2 Spring Boot中的Mock注解
3.2.1 @MockBean vs @Mock
// @MockBean:Spring容器中的Bean,会替换真实Bean
@MockBean
private UserService userService;// @Mock:普通Mock对象,需要手动注入
@Mock
private UserService userService;@InjectMocks
private YourService yourService; // 自动注入Mock对象
3.2.2 @SpyBean vs @Spy
// @SpyBean:部分Mock,保留真实行为,可选择性Mock方法
@SpyBean
private UserService userService;// @Spy:普通Spy对象
@Spy
private UserService userService = new UserServiceImpl();
3.3 常见框架对比
| 框架 | 特点 | 适用场景 |
|---|---|---|
| Mockito | 简单易用,功能强大 | 单元测试,Spring Boot项目 |
| EasyMock | 早期框架,语法复杂 | 遗留项目 |
| PowerMock | 可Mock静态方法、私有方法 | 需要Mock静态方法的场景 |
| JMockit | 功能全面,语法灵活 | 复杂测试场景 |
四、重难点分析
4.1 难点一:Mock静态方法
问题:Mockito默认不支持Mock静态方法
解决方案:
- 使用PowerMock(不推荐,已停止维护)
@RunWith(PowerMockRunner.class)
@PrepareForTest({DateUtils.class})
public class StaticMethodTest {@Testpublic void testStaticMethod() {PowerMockito.mockStatic(DateUtils.class);when(DateUtils.getCurrentTime()).thenReturn(1234567890L);}
}
- 使用MockedStatic(Mockito 3.4+)(推荐)
@Test
void testStaticMethod() {try (MockedStatic<DateUtils> mockedStatic = mockStatic(DateUtils.class)) {mockedStatic.when(DateUtils::getCurrentTime).thenReturn(1234567890L);// 测试代码}
}
- 重构代码(最佳实践)
- 将静态方法改为实例方法
- 使用依赖注入,便于测试
4.2 难点二:Mock私有方法
问题:无法直接Mock私有方法
解决方案:
-
测试公有方法(推荐)
- 私有方法通常通过公有方法间接测试
- 如果私有方法复杂,考虑提取为独立类
-
使用反射(不推荐)
Method method = YourClass.class.getDeclaredMethod("privateMethod", String.class);
method.setAccessible(true);
Object result = method.invoke(object, "param");
- 使用PowerMock(不推荐)
4.3 难点三:Mock final类和final方法
问题:Mockito无法Mock final类和方法
解决方案:
- 使用Mockito 2.1+:支持Mock final类(需要配置)
// 在src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
// 创建文件,内容:mock-maker-inline
- 重构代码:避免使用final(如果可能)
4.4 难点四:Mock链式调用
问题:Mock对象的方法返回自身,形成链式调用
解决方案:
// 错误示例
when(mockObject.method1().method2()).thenReturn(result);// 正确示例
when(mockObject.method1()).thenReturn(mockObject);
when(mockObject.method2()).thenReturn(result);// 或者使用链式Mock
when(mockObject.method1().method2()).thenReturn(result); // 需要配置
4.5 难点五:Mock异常场景
问题:需要测试异常情况
解决方案:
// Mock抛出异常
when(service.method()).thenThrow(new RuntimeException("测试异常"));// Mock多次调用返回不同结果
when(service.method()).thenReturn(result1).thenReturn(result2).thenThrow(new RuntimeException());// Mock void方法抛出异常
doThrow(new RuntimeException()).when(service).voidMethod();
4.6 难点六:Mock Spring Bean的循环依赖
问题:Spring Bean之间存在循环依赖,Mock时出现问题
解决方案:
- 使用@MockBean替换整个Bean
@MockBean
private ServiceA serviceA; // 会替换Spring容器中的Bean
- 使用@Primary + @MockBean
@Primary
@MockBean
private ServiceA serviceA;
- 重构代码:消除循环依赖(最佳实践)
五、使用技巧
5.1 最佳实践
5.1.1 使用BDD风格
// Given-When-Then模式
@Test
public void testUserService() {// Given:准备测试数据User user = new User();user.setId(1L);when(userRepository.findById(1L)).thenReturn(Optional.of(user));// When:执行被测试方法User result = userService.getUser(1L);// Then:验证结果assertNotNull(result);assertEquals(1L, result.getId());verify(userRepository, times(1)).findById(1L);
}
5.1.2 使用ArgumentMatchers
// 精确匹配
when(service.method("exact")).thenReturn(result);// 任意参数
when(service.method(anyString())).thenReturn(result);// 自定义匹配器
when(service.method(argThat(arg -> arg.length() > 5))).thenReturn(result);// 多个参数
when(service.method(anyString(), anyInt(), isNull())).thenReturn(result);
5.1.3 验证方法调用
// 验证调用次数
verify(service, times(1)).method();
verify(service, never()).method();
verify(service, atLeast(1)).method();
verify(service, atMost(3)).method();// 验证调用顺序
InOrder inOrder = inOrder(service1, service2);
inOrder.verify(service1).method1();
inOrder.verify(service2).method2();// 验证无其他交互
verifyNoMoreInteractions(service);
verifyZeroInteractions(service);
5.1.4 使用Answer处理复杂逻辑
when(service.method(anyString())).thenAnswer(invocation -> {String param = invocation.getArgument(0);// 根据参数返回不同结果if (param.startsWith("test")) {return "test result";}return "default result";
});
5.2 常见陷阱与避免
5.2.1 过度Mock
问题:Mock了不应该Mock的对象
解决:
- 只Mock外部依赖(数据库、网络、文件系统)
- 不Mock值对象(DTO、VO)
- 不Mock简单工具类
5.2.2 Mock验证过于严格
问题:验证了不必要的调用细节
解决:
- 只验证关键业务逻辑
- 使用
atLeast()、atMost()而不是times() - 避免验证内部实现细节
5.2.3 忘记重置Mock
问题:测试之间Mock状态污染
解决:
@Before
public void setUp() {reset(mockObject); // 重置Mock
}
5.2.4 Mock真实对象
问题:Mock了不应该Mock的真实对象
解决:
- 使用
@Spy或@SpyBean保留部分真实行为 - 明确区分Mock和真实对象的使用场景
5.3 性能优化技巧
5.3.1 使用@MockitoSettings
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class TestClass {// 宽松模式,未使用的Mock不会报错
}
5.3.2 批量Mock
@Mock
private Service1 service1;
@Mock
private Service2 service2;
@Mock
private Service3 service3;// 使用@InjectMocks自动注入
@InjectMocks
private YourService yourService;
5.4 调试技巧
5.4.1 打印Mock调用信息
// 使用ArgumentCaptor捕获参数
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(service).method(captor.capture());
System.out.println("Captured: " + captor.getValue());
5.4.2 使用Mockito的调试模式
// 启用详细日志
Mockito.debug();
// 或使用Mockito.withSettings().verboseLogging()
六、小结
6.1 Mock技术总结
Mock技术是现代软件开发中不可或缺的测试工具,它能够:
- 提高测试效率:快速执行,不依赖外部环境
- 增强测试覆盖:轻松模拟各种异常和边界情况
- 改善代码质量:促进依赖注入和接口设计
- 加速开发流程:支持并行开发和持续集成
6.2 适用原则
- ✅ 应该Mock:外部服务、数据库、文件系统、网络请求
- ✅ 可以Mock:复杂业务逻辑、耗时操作、随机数生成
- ❌ 不应该Mock:值对象、简单工具类、被测试类本身
6.3 学习路径建议
- 初级阶段:掌握
@Mock、@MockBean、when().thenReturn() - 中级阶段:学习
ArgumentMatchers、verify()、异常Mock - 高级阶段:掌握
Answer、ArgumentCaptor、静态方法Mock - 专家阶段:理解Mock原理,能够解决复杂场景问题
6.4 项目实践建议
基于当前项目(Spring Boot + MyBatis Plus),建议:
- 统一Mock框架:使用Mockito作为主要Mock框架
- 建立Mock规范:制定团队Mock使用规范
- Mock数据管理:对于大数据接口等,使用数据库存储Mock数据(如
BmBigdataMock) - 测试覆盖率:结合JaCoCo等工具,确保测试覆盖率
- 持续改进:定期Review测试代码,优化Mock使用
6.5 参考资料
- Mockito官方文档
- Spring Boot Testing
- 《单元测试的艺术》- Roy Osherove
- 《测试驱动开发》- Kent Beck
