You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用JUnit、Mockito测试时如何在临时CSV文件删除前校验内容

可行实现方案

完全不需要修改生产代码,利用Mockito的自定义Answer机制就能精准卡在文件删除前完成内容校验,以下是可直接落地的实现思路:

核心原理

你的业务方法执行顺序是固定的:

  1. saveInCsv() 执行完成时,CSV文件已经写完刷盘,文件完整可用
  2. 进入sendEmail()方法,调用mailUtil.send()发送带附件的邮件
  3. 等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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 21:12:25