使用JUnit、Mockito测试时如何在临时CSV文件删除前校验内容
可行实现方案
完全不需要修改生产代码,利用Mockito的自定义Answer机制就能精准卡在文件删除前完成内容校验,以下是可直接落地的实现思路:
核心原理
你的业务方法执行顺序是固定的:
saveInCsv()执行完成时,CSV文件已经写完刷盘,文件完整可用- 进入
sendEmail()方法,调用mailUtil.send()发送带附件的邮件 - 等
mailUtil.send()执行完成(无论正常返回还是抛异常),才会进入finally块执行deleteFile()删除文件
也就是说,mailUtil.send()被调用的窗口期,文件是100%存在且内容完整的,只要在这个节点插入校验逻辑即可。
方案1:拦截MailUtil发送方法(侵入性最低,推荐)
直接mock依赖的mailUtil工具类,给send方法绑定自定义Answer,在send方法被调用的时刻读取附件文件做校验,完全不改变原有代码执行流程:
@ExtendWith(MockitoExtension.class) class EmailServiceTest { @Mock private MailUtil mailUtil; @InjectMocks private EmailService emailService; // 替换为你自己的被测类名 @Test void testGenerateEmailFileCsvContent() throws Exception { // 1. 构造测试预期数据,如果业务数据是从外部依赖获取,提前mock对应依赖返回测试值即可 List<String> expectedHeader = List.of("订单号", "下单时间", "金额"); List<List<String>> expectedData = List.of( List.of("ORD001", "2024-06-01", "99.00"), List.of("ORD002", "2024-06-02", "199.00") ); // 2. 给mailUtil的send方法绑定自定义逻辑,参数匹配规则按你实际的send方法签名调整 doAnswer(invocation -> { // 从send方法入参中拿到CSV附件对象,参数索引按你实际方法的参数位置修改 File csvAttachment = invocation.getArgument(2, File.class); // 读取文件内容做校验,建议用和写文件一致的CSV解析器(比如commons-csv)避免逗号转义问题 try (CSVParser parser = CSVParser.parse(csvAttachment, StandardCharsets.UTF_8, CSVFormat.EXCEL.withHeader())) { // 校验表头 assertEquals(expectedHeader, parser.getHeaderNames()); // 校验数据行 List<CSVRecord> records = parser.getRecords(); assertEquals(expectedData.size(), records.size()); for (int i = 0; i < expectedData.size(); i++) { CSVRecord record = records.get(i); List<String> actualRow = StreamSupport.stream(record.spliterator(), false).toList(); assertEquals(expectedData.get(i), actualRow); } } // 如果要测试邮件发送异常的场景,在这里抛出对应异常即可,会正常触发finally的删除逻辑 // throw new MessagingException("模拟邮件发送失败"); return null; // void方法返回null即可 }).when(mailUtil).send(any(), any(), any(File.class)); // 3. 执行被测方法 emailService.generateEmailFile(); // 4. 额外校验:确认文件最终被删除,finally逻辑生效 assertFalse(new File("hello.csv").exists()); verify(mailUtil, times(1)).send(any(), any(), any(File.class)); } // 兜底删除测试文件,避免异常场景下文件残留影响其他用例 @AfterEach void cleanUp() { File csv = new File("hello.csv"); if (csv.exists()) { csv.delete(); } } }
方案2:拦截deleteFile方法(适配特殊代码结构)
如果你的sendEmail方法内部对附件做了二次封装,没法直接从mailUtil.send的入参拿到文件对象,可以用Mockito的spy机制拦截deleteFile方法,在执行删除前读取文件校验。
注意:拦截私有方法需要引入
mockito-inline依赖,Mockito 3.4+版本默认已支持该能力。
@Test void testCsvContentByInterceptDelete() throws Exception { // 构造被测类的spy对象,手动注入mock依赖 EmailService spyService = spy(new EmailService(mailUtil)); // 构造测试预期数据逻辑和方案1一致 // 拦截私有deleteFile方法 doAnswer(invocation -> { File targetCsv = invocation.getArgument(0, File.class); // 这里读取文件做校验,逻辑和方案1完全一致 try (CSVParser parser = CSVParser.parse(targetCsv, StandardCharsets.UTF_8, CSVFormat.EXCEL.withHeader())) { // 断言校验逻辑省略 } // 校验完成后调用真实删除方法,不破坏原有逻辑 return invocation.callRealMethod(); }).when(spyService, "deleteFile", any(File.class)); spyService.generateEmailFile(); // 后续验证逻辑和方案1一致 }
注意事项
- 读取CSV时不要直接用
String.split(","),遇到字段内含逗号、换行、双引号的场景会解析错误,必须和你写文件时用的CSV格式保持一致,用对应CSV解析器读取。 - 如果
hello.csv的生成路径不是项目相对路径,读取时替换为实际生成路径即可,不需要修改生产代码。 - 如果要测试多场景(邮件发送成功/失败),只需要在Answer中调整返回值或者抛出对应异常即可,不需要重复写文件读取逻辑。
内容的提问来源于stack exchange,提问作者sandhu
相关产品推荐
相关产品推荐

