当前位置: 首页 > article >正文

JUnit 5单元测试(三)—— Mockito 模拟实战:从零构建隔离测试环境

1. 为什么需要Mockito隔离测试环境第一次接触单元测试时我遇到过这样的尴尬场景测试一个订单支付功能每次运行测试都要真实调用支付宝接口不仅测试速度慢还因为网络波动经常失败。更糟的是有次测试数据污染了生产环境差点引发线上事故。这时候我才真正理解Mockito的价值——它能让测试像在无菌实验室里进行完全掌控所有变量。现代系统常见的三大测试痛点Mockito都能完美解决外部依赖不可控比如数据库查询可能超时、第三方接口可能限流。通过Mockito模拟的DAO层对象可以立即返回预设数据不再受网络或服务状态影响。我做过对比测试真实数据库查询平均需要200ms而Mock对象仅需2ms。测试数据污染曾经有个同事在测试中误删了用户表数据。用Mock技术后所有数据库操作实际只在内存中模拟根本不会触及真实数据存储。复杂场景难以构造比如测试支付失败重试逻辑真实环境很难让支付网关连续报错。用Mockito的thenThrow()可以轻松模拟连续异常// 模拟支付服务连续3次超时 when(paymentService.process(any())) .thenThrow(new TimeoutException()) .thenThrow(new TimeoutException()) .thenReturn(success);实际项目中这些场景特别适合用Mockito微服务间的Feign/RestTemplate调用MyBatis/JPA数据库操作Redis缓存读写Kafka/RabbitMQ消息生产消费外部SDK如短信、OSS存储2. 快速搭建Mockito测试环境2.1 依赖配置的坑与技巧最近Mockito 5.x开始要求JDK11多数项目还在用JDK8这里推荐4.x最终版4.11.0。除了核心库有两个扩展包特别实用!-- 基础mock功能 -- dependency groupIdorg.mockito/groupId artifactIdmockito-core/artifactId version4.11.0/version scopetest/scope /dependency !-- 支持JUnit5注解 -- dependency groupIdorg.mockito/groupId artifactIdmockito-junit-jupiter/artifactId version4.11.0/version scopetest/scope /dependency !-- 静态方法mock谨慎使用 -- dependency groupIdorg.mockito/groupId artifactIdmockito-inline/artifactId version4.11.0/version scopetest/scope /dependency踩坑提醒静态方法mock需要mockito-inline但可能引发内存泄漏。建议用try-with-resources包裹try (MockedStaticMyStaticClass mocked mockStatic(MyStaticClass.class)) { // 测试代码 }2.2 三种Mock初始化方式对比方式一手动创建适合简单测试PaymentService paymentService mock(PaymentService.class);方式二注解手动管理已过时Mock PaymentService paymentService; BeforeEach void setup() { MockitoAnnotations.openMocks(this); }方式三JUnit5扩展推荐ExtendWith(MockitoExtension.class) class PaymentTest { Mock PaymentService paymentService; Test void testPayment() { // 直接使用mock对象 } }实际项目中95%的情况推荐方式三。它自动管理mock生命周期支持构造函数注入还能通过方法参数注入Test void testWithParamInjection(Mock OrderDao orderDao) { // 每个测试方法独享mock实例 }3. Mockito核心功能实战3.1 行为模拟三板斧基础模拟when-thenReturn// 模拟查询返回特定订单 when(orderDao.findById(anyLong())) .thenReturn(Optional.of(new Order(1L, PAID))); // 模拟连续不同返回值 when(orderDao.getStatus()) .thenReturn(CREATED) .thenReturn(PAID) .thenReturn(COMPLETED);异常模拟thenThrow// 模拟支付超时 when(paymentGateway.process(any())) .thenThrow(new TimeoutException(网络超时)); // 模拟账户余额不足 when(accountService.debit(any())) .thenThrow(new BusinessException(余额不足));void方法模拟doNothing// 模拟消息发送不实际调用 doNothing().when(messageQueue).send(any());3.2 参数匹配的进阶用法精确匹配容易导致测试脆弱推荐使用参数匹配器// 任意字符串特定金额 when(paymentService.checkBalance(anyString(), eq(100.0))) .thenReturn(true); // 自定义匹配器 when(orderDao.findByCriteria(argThat(criteria - criteria.getStatus().equals(PAID)))) .thenReturn(paidOrders);注意陷阱一旦使用参数匹配器所有参数都必须用匹配器// 错误写法第二个参数没用匹配器 when(service.method(anyString(), 123)).thenReturn(...); // 正确写法 when(service.method(anyString(), eq(123))).thenReturn(...);3.3 验证调用行为验证是Mockito区别于普通桩Stub的核心能力// 基本验证 verify(orderDao).findById(1L); // 验证调用次数 verify(paymentService, times(3)).retry(any()); // 验证超时时间内完成 verify(notificationService, timeout(100)) .sendSms(any()); // 验证调用顺序 InOrder inOrder inOrder(serviceA, serviceB); inOrder.verify(serviceA).prepare(); inOrder.verify(serviceB).execute();遇到过的一个经典案例需要验证支付成功后必须且仅能调用一次记账服务verify(accountingService, times(1)) .recordPayment(eq(orderId), anyDouble()); verifyNoMoreInteractions(accountingService);4. 高级技巧Spy与依赖注入4.1 真实对象监控Spy当需要部分mock真实对象时Spy是更好的选择Spy RealService realService new RealService(); Test void testSpy() { // 真实方法会被调用 String result realService.process(input); // 可以覆盖特定方法 doReturn(mocked).when(realService).process(special); verify(realService).process(anyString()); }踩坑记录Spy对象需要初始化真实实例否则会NPE// 错误写法 Spy RealService realService; // 未初始化 // 正确写法 Spy RealService realService new RealService();4.2 依赖注入魔法InjectMocks处理复杂依赖关系的终极方案ExtendWith(MockitoExtension.class) class OrderServiceTest { Mock PaymentGateway paymentGateway; Mock InventoryService inventoryService; InjectMocks OrderService orderService; // 自动注入上述mock Test void placeOrder() { when(paymentGateway.charge(any())) .thenReturn(SUCCESS); when(inventoryService.reserve(any())) .thenReturn(true); Order order orderService.placeOrder(new Order()); assertNotNull(order.getId()); } }实际项目中的经验法则被InjectMocks标记的类会被实例化通过构造函数或setter注入Mock对象优先使用构造函数注入更明确对于复杂继承关系可能需要配合Spy使用5. 测试设计模式与最佳实践5.1 分层测试策略单元测试金字塔应用底层DAO大量使用Mockito模拟数据库驱动中间Service混合真实逻辑与mock依赖上层Controller可考虑部分集成测试WebMvcTest(OrderController.class) class OrderControllerTest { Autowired MockMvc mockMvc; MockBean OrderService orderService; Test void shouldReturnOrder() throws Exception { when(orderService.findById(anyLong())) .thenReturn(new Order(1L, PAID)); mockMvc.perform(get(/orders/1)) .andExpect(status().isOk()) .andExpect(jsonPath($.status).value(PAID)); } }5.2 可维护性技巧创建MockUtils封装常用mock逻辑class PaymentMocks { static void mockSuccess(PaymentService mock) { when(mock.process(any())) .thenReturn(new Result(true, 支付成功)); } }使用BeforeEach初始化避免重复代码BeforeEach void setup() { mockSuccess(paymentService); mockInventoryReserved(inventoryService); }自定义Answer实现复杂逻辑when(redisTemplate.opsForValue().get(anyString())) .thenAnswer(inv - { String key inv.getArgument(0); return localCache.get(key); });5.3 常见反模式过度mock把业务逻辑都mock掉失去测试意义验证过度验证每个getter/setter调用忽略线程安全在多线程测试中共享mock状态静态方法滥用导致测试相互污染记得有次排查测试偶发失败最终发现是静态mock未关闭// 错误示范忘记关闭 mockStatic(UtilityClass.class); when(UtilityClass.method()).thenReturn(...); // 正确做法 try (MockedStaticUtilityClass mocked mockStatic(UtilityClass.class)) { when(UtilityClass.method()).thenReturn(...); // 测试代码 }6. 真实项目案例订单支付流程假设我们有如下支付流程检查库存冻结库存调用支付更新订单状态扣减库存发送通知完整测试案例ExtendWith(MockitoExtension.class) class OrderPaymentTest { Mock InventoryService inventory; Mock PaymentGateway gateway; Mock NotificationService notification; InjectMocks OrderService service; Test void shouldProcessPaymentSuccessfully() { // 准备mock when(inventory.check(anyLong())).thenReturn(true); doNothing().when(inventory).freeze(anyLong()); when(gateway.charge(any())).thenReturn(SUCCESS); // 执行测试 PaymentResult result service.processPayment(1L, 100.0); // 验证行为 assertTrue(result.isSuccess()); verify(inventory).check(1L); verify(inventory).freeze(1L); verify(gateway).charge(100.0); verify(notification).sendPaymentSuccess(1L); } Test void shouldUnfreezeWhenPaymentFails() { when(inventory.check(anyLong())).thenReturn(true); when(gateway.charge(any())) .thenThrow(new PaymentException(余额不足)); assertThrows(PaymentException.class, () - service.processPayment(1L, 100.0)); verify(inventory).unfreeze(1L); } }关键验证点正向流程各环节调用顺序异常时的补偿机制如库存解冻边界条件库存不足、支付失败等并发情况下的状态一致性在电商项目中这种测试模式帮助我们发现了支付状态与库存不一致的严重bug而这一切测试都不需要启动数据库或真正的支付网关。

相关文章:

JUnit 5单元测试(三)—— Mockito 模拟实战:从零构建隔离测试环境

1. 为什么需要Mockito隔离测试环境 第一次接触单元测试时,我遇到过这样的尴尬场景:测试一个订单支付功能,每次运行测试都要真实调用支付宝接口,不仅测试速度慢,还因为网络波动经常失败。更糟的是,有次测试数…...

Java项目Loom化失败率高达63%?(2026 Gartner调研首发:3个被90%团队忽略的阻塞调用陷阱)

第一章:Java项目Loom化失败率高达63%?——2026 Gartner调研核心洞察2026年Gartner发布的《Java生态现代化成熟度报告》显示,在已启动虚拟线程(Virtual Threads)迁移的1,247个中大型Java项目中,63.2%未能完成…...

从冷战到海湾战争:聊聊‘长曲棍球’雷达卫星如何用合成孔径雷达(SAR)穿透云层和烟雾

透视战场的“天眼”:合成孔径雷达如何突破气象屏障重塑军事侦察 1988年12月2日,美国佛罗里达州肯尼迪航天中心的发射架上,亚特兰蒂斯号航天飞机搭载着一个特殊载荷升空——这个代号“长曲棍球”的绝密项目,标志着合成孔径雷达(SAR…...

0078.子集

题目链接 78. 子集 - 力扣(LeetCode) 题目描述 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 题目示例 示例 1 …...

5步掌握Camera Shakify:为Blender动画注入真实相机抖动效果

5步掌握Camera Shakify:为Blender动画注入真实相机抖动效果 【免费下载链接】camera_shakify 项目地址: https://gitcode.com/gh_mirrors/ca/camera_shakify 你是否曾经制作过完美的Blender动画,却发现相机运动过于平滑、机械,缺少电…...

ConvNeXt-Tiny模型在PyTorch上的保姆级训练教程(附完整代码与花分类实战)

ConvNeXt-Tiny模型在PyTorch上的保姆级训练教程(附完整代码与花分类实战) ConvNeXt作为2022年计算机视觉领域的重要突破,重新定义了纯卷积神经网络的可能性。本教程将带您从零开始,在PyTorch框架下完成ConvNeXt-Tiny模型的完整训练…...

保姆级避坑指南:在Ubuntu 20.04上从源码编译ORB-SLAM3(含ROS Noetic适配)

保姆级避坑指南:在Ubuntu 20.04上从源码编译ORB-SLAM3(含ROS Noetic适配) 当你在Ubuntu 20.04上尝试编译ORB-SLAM3时,可能会遇到各种依赖问题、版本冲突和配置错误。本文将深入解析这些常见问题,并提供经过验证的解决方…...

告别ArcGIS手动操作:用Python脚本批量处理MCD12Q2植被物候数据(附完整代码)

用Python全自动处理MODIS物候数据:从HDF到生长季分析的完整解决方案 在植被物候研究中,MCD12Q2数据集因其高时间分辨率和全球覆盖能力成为不可替代的数据源。但面对动辄数十GB的HDF文件,传统ArcGIS点选操作不仅效率低下,更难以应对…...

SAP S/4HANA Cloud 公有云实施:广州企业服务商选型与落地实践

随着数字化转型的深入推进,越来越多的广州企业开始关注SAP ERP公有云解决方案。相比传统本地部署,公有云版本具有部署周期短、运维成本低、弹性扩展灵活等优势,特别适合中大型企业快速构建数字化核心能力。为什么选择SAP ERP公有云&#xff1…...

别再死磕理论了!用PCL+KinectFusion手把手教你从照片到3D模型(保姆级避坑指南)

从零实现3D建模:基于PCL与KinectFusion的实战避坑手册 当我在研究生实验室第一次尝试用Kinect扫描物体生成3D模型时,连续三天的环境配置失败几乎让我放弃。直到发现那个被埋没在GitHub issue里的OpenCL驱动解决方案,才明白三维重建的入门门槛…...

软件测试计划模板

一、文档概述 1.1 文档目的 本文档旨在明确本次软件测试的测试目标、范围、策略、资源、进度、风险等核心内容,规范测试全流程工作,指导所有测试参与人员有序开展测试活动,保障测试工作高效、高质量完成,验证软件产品是否满足需求规格、业务场景及用户使用要求,确保产品…...

SQL多表关联查询中提升可读性的规范_合理缩进与表别名定义

SQL表别名须用AS显式声明且具业务语义,如usr/ord;JOIN条件需垂直对齐、ON独行缩进;SELECT字段必带表前缀;CTE命名要表达意图,仅在必要时展开。表别名必须用 AS 显式声明,且命名要有语义很多人图省事写 SELE…...

从IP调用量看AI落地热力图:哪些城市的AI应用最活跃?

AI产业的繁荣,除了看企业数量和融资规模,还有一个更接地气的观察维度——AI API的实际调用量。IP调用量的地理分布,能直观回答一个问题:AI到底在哪些城市真正被“用”起来了? 一、AI调用量爆发:一个“用脚…...

三分钟上手AI变声神器RVC:免费开源语音转换终极指南

三分钟上手AI变声神器RVC&#xff1a;免费开源语音转换终极指南 【免费下载链接】Retrieval-based-Voice-Conversion-WebUI Easily train a good VC model with voice data < 10 mins! 项目地址: https://gitcode.com/GitHub_Trending/re/Retrieval-based-Voice-Conversio…...

深入S2A-Net的‘对齐卷积’:如何让卷积网络‘看懂’旋转的物体?

旋转物体检测的革命&#xff1a;S2A-Net如何用对齐卷积突破传统极限 当无人机从高空俯瞰大地&#xff0c;传回的图像中船只、车辆、飞机以各种角度随意分布&#xff1b;当卫星扫描地球表面&#xff0c;建筑物、桥梁、运动场呈现出千变万化的方向——这些场景对计算机视觉系统提…...

哔哩下载姬完整教程:5分钟掌握B站视频下载与处理终极方案

哔哩下载姬完整教程&#xff1a;5分钟掌握B站视频下载与处理终极方案 【免费下载链接】downkyi 哔哩下载姬downkyi&#xff0c;哔哩哔哩网站视频下载工具&#xff0c;支持批量下载&#xff0c;支持8K、HDR、杜比视界&#xff0c;提供工具箱&#xff08;音视频提取、去水印等&am…...

车载问答系统开发不再踩坑:Dify v0.12.3适配Autosar AP平台完整技术白皮书(含ASAM MCD-2 MC接口映射表)

第一章&#xff1a;车载问答系统开发不再踩坑&#xff1a;Dify v0.12.3适配Autosar AP平台完整技术白皮书&#xff08;含ASAM MCD-2 MC接口映射表&#xff09;核心适配约束与平台边界定义 Dify v0.12.3 在 Autosar AP 平台部署需严格遵循 ISO 26262 ASIL-B 软件架构约束。运行时…...

Dify合规问答配置失效真相:3家持牌机构被罚案例背后的2个底层配置逻辑漏洞

第一章&#xff1a;Dify合规问答配置失效真相&#xff1a;3家持牌机构被罚案例背后的2个底层配置逻辑漏洞近期&#xff0c;银保监会通报的三起AI问答系统违规事件中&#xff0c;涉事机构均使用Dify搭建面向公众的金融知识问答服务&#xff0c;但其“敏感词拦截”与“答案溯源强…...

【Dify工业知识库权威配置白皮书】:基于27家制造企业落地数据,提炼出的6类非标文档适配方案

第一章&#xff1a;Dify工业知识库配置的核心价值与落地全景 在智能制造与工业数字化转型加速推进的背景下&#xff0c;Dify作为开源大模型应用开发平台&#xff0c;其工业知识库配置能力正成为企业构建可解释、可审计、可迭代智能服务的关键支点。区别于通用场景的知识管理&am…...

如何高效使用PZEM-004T v3.0电力监测库:专业开发者的完整实战指南

如何高效使用PZEM-004T v3.0电力监测库&#xff1a;专业开发者的完整实战指南 【免费下载链接】PZEM-004T-v30 Arduino library for the Updated PZEM-004T v3.0 Power and Energy meter 项目地址: https://gitcode.com/gh_mirrors/pz/PZEM-004T-v30 PZEM-004T v3.0是一…...

【Dify医疗问答调试实战指南】:20年AI工程专家亲授5大高频故障定位法与秒级修复技巧

第一章&#xff1a;Dify医疗问答调试的核心挑战与认知升级在医疗垂直领域部署Dify构建问答系统时&#xff0c;调试过程远非通用场景的简单复用。模型输出的临床严谨性、术语一致性、上下文依赖强度以及合规性边界&#xff0c;共同构成了区别于常规RAG应用的独特挑战谱系。语义漂…...

ARM裸机到ThreadX:手把手教你移植系统时钟与中断处理(基于S3C2440)

ARM裸机到ThreadX&#xff1a;S3C2440系统时钟与中断移植实战指南 从零构建嵌入式实时系统的核心机制 在嵌入式开发领域&#xff0c;RTOS移植往往是最具挑战性的环节之一。当开发者拿到一块全新的ARM开发板时&#xff0c;如何为其适配实时操作系统成为项目推进的关键。本文将…...

PyTorch推理扩展实战:用Ray Data轻松实现多机多卡并行

单机 PyTorch 模型跑推理没什么问题&#xff0c;但数据量一旦上到万级、百万级&#xff0c;瓶颈就暴露出来了&#xff1a;内存不够、GPU 利用率低、I/O 拖后腿&#xff0c;更别说还要考虑容错和多机扩展。传统做法是自己写多线程 DataLoader、管理批次队列、手动调度 GPU 资源&…...

用PaddlePaddle动态图复现ResNet50,从零搭建一个眼底病变分类器(附完整代码)

基于PaddlePaddle动态图的ResNet50眼底病变分类实战指南 在医疗影像分析领域&#xff0c;自动化的疾病筛查系统正逐渐成为临床医生的得力助手。眼底病变的早期发现对预防视力损伤至关重要&#xff0c;而深度学习技术为这一任务提供了新的可能性。本文将带领读者使用PaddlePadd…...

OFA图像语义蕴含模型保姆级部署指南:从零到一搭建Web应用

OFA图像语义蕴含模型保姆级部署指南&#xff1a;从零到一搭建Web应用 1. 项目概述与技术背景 1.1 什么是图像语义蕴含 图像语义蕴含&#xff08;Visual Entailment&#xff09;是多模态AI领域的一项重要任务&#xff0c;它需要系统判断给定的文本描述是否能够从图像内容中逻…...

Flink+SLS 云原生组合:构建阿里云 OpenAPI 网关实时监控体系,故障发现提速至秒级!

背景与挑战阿里云开放平台&#xff08;OpenAPI&#xff09;是开发者管理云上资源的标准入口&#xff0c;承载了几乎所有云产品的对外接口&#xff0c;满足客户自动化运维与云资源管控的核心诉求。随着企业对自动化的依赖日益加深&#xff0c;OpenAPI 的稳定性建设变得至关重要。…...

Skyeye云智能制造v3.19.2发布:零代码平台,功能升级,开发效率大提升!

【Skyeye云智能制造简介】Skyeye云智能制造是智能制造一体化&#xff0c;采用SpringBoot UNI - APP Ant Design Vue的零代码平台开发模式。它包含100多种电子流程&#xff0c;以及CRM、PM、ERP、MES、ADM、OA、EHR、AI、项目、商城、财务、多班次考勤、薪资、招聘、云售后、论…...

Kubernetes 创造者投身自主 AI,Stacklok 能否打造 AI 领域的“Kubernetes 时刻”?

聚焦责任问题McLuckie 在 2023 年初创立了 Stacklok。他的搭档 Beda 在 2022 年“半退休”&#xff0c;加入是因这是“行业的一个非凡时刻”&#xff0c;有机会用专业知识解决企业关键问题。McLuckie 称最大问题是责任&#xff0c;智能体无法对工作负责&#xff0c;企业仍要对结…...

5G手机信号突然变差?可能是RRC连接释放的锅,附排查思路

5G手机信号突然变差&#xff1f;可能是RRC连接释放的锅&#xff0c;附排查思路 你是否遇到过这样的场景&#xff1a;手机明明显示5G信号满格&#xff0c;但刷视频却频繁缓冲&#xff0c;游戏延迟飙升&#xff0c;甚至微信消息都发不出去&#xff1f;这种"假信号"问题…...

告别激活烦恼:KMS_VL_ALL_AIO智能激活工具完全指南

告别激活烦恼&#xff1a;KMS_VL_ALL_AIO智能激活工具完全指南 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 你是否曾因为Windows系统突然弹出激活提醒而中断重要工作&#xff1f;是否在关键时…...