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

Mockito如何精确校验含new PrintWriter的方法入参

Mockito校验内部new生成参数的精确匹配方案

校验失败的核心原因是:PrintWriter没有重写equals()方法,Mockito默认的参数比对逻辑会校验两个对象是否为同一个内存实例,测试代码里new的PrintWriter和业务代码里new的PrintWriter是完全独立的两个对象,自然比对失败。
可以通过以下方案实现精确校验:

方案1:使用ArgumentCaptor+自定义校验(无需修改业务代码)

通过参数捕获器拿到业务代码实际传入的PrintWriter实例,再校验它绑定的文件路径是否符合预期;第二个参数因为List默认实现了按内容比对的equals()方法,直接用eq()包裹预期值即可做精确匹配。
示例代码:

import org.mockito.ArgumentCaptor;
import java.io.PrintWriter;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.util.List;

// 1. 定义预期值
String expectedCsvPath = ".\\xyz.csv";
List expectedList = TestUtil.getSomeList();

// 2. 执行业务测试逻辑
// 此处替换为实际调用被测方法的代码,比如xxxService.exportCsv();

// 3. 校验方法调用次数,同时捕获PrintWriter参数
ArgumentCaptor<PrintWriter> writerCaptor = ArgumentCaptor.forClass(PrintWriter.class);
verify(csvWriter, times(1)).writeCsv(writerCaptor.capture(), eq(expectedList));

// 4. 校验捕获到的PrintWriter绑定的路径是否正确
PrintWriter actualWriter = writerCaptor.getValue();
Field outField = PrintWriter.class.getDeclaredField("out");
outField.setAccessible(true);
Object innerObj = outField.get(actualWriter);
String actualPath = null;
// 逐层拆包PrintWriter内部包装的流对象,拿到最终的文件路径
while (innerObj != null) {
    if (innerObj instanceof FileOutputStream) {
        Field pathField = FileOutputStream.class.getDeclaredField("path");
        pathField.setAccessible(true);
        actualPath = (String) pathField.get(innerObj);
        break;
    }
    Field nextOut = innerObj.getClass().getDeclaredField("out");
    nextOut.setAccessible(true);
    innerObj = nextOut.get(innerObj);
}
// 断言路径匹配
assertEquals(expectedCsvPath, actualPath);

方案2:使用argThat自定义参数匹配器(无需修改业务代码)

不需要单独捕获参数,直接在verify语句中自定义PrintWriter的匹配规则,匹配时校验实例绑定的文件路径即可:

String expectedCsvPath = ".\\xyz.csv";
List expectedList = TestUtil.getSomeList();

verify(csvWriter, times(1)).writeCsv(
    argThat(writer -> {
        try {
            Field outField = PrintWriter.class.getDeclaredField("out");
            outField.setAccessible(true);
            Object innerObj = outField.get(writer);
            while (innerObj != null) {
                if (innerObj instanceof FileOutputStream) {
                    Field pathField = FileOutputStream.class.getDeclaredField("path");
                    pathField.setAccessible(true);
                    return expectedCsvPath.equals(pathField.get(innerObj));
                }
                Field nextOut = innerObj.getClass().getDeclaredField("out");
                nextOut.setAccessible(true);
                innerObj = nextOut.get(innerObj);
            }
        } catch (Exception e) {
            return false;
        }
        return false;
    }),
    eq(expectedList)
);

更推荐的可测试性优化方案(从根源解决问题)

上面两种方案都用到了反射访问JDK内部字段,不同JDK版本如果修改了流内部的字段名,测试代码就会失效。更好的做法是调整业务代码结构,把PrintWriter的创建逻辑抽离成可注入的工厂类:

  • 定义工厂接口PrintWriterFactory,包含方法PrintWriter create(String filePath)
  • 业务类中注入PrintWriterFactory实例,生成PrintWriter时调用factory.create(csvPath),而不是直接new
  • 单元测试中直接mockPrintWriterFactory,就可以直接校验create方法接收到的路径参数是否为.\xyz.csv,不需要再反射拆包校验PrintWriter内部状态,代码稳定性更高。

注意:Mockito的参数匹配规则要求,只要某一个参数用了匹配器(比如argThat、capture、any),其余所有参数都必须用匹配器包裹,所以第二个参数的预期列表必须用eq()包裹,不能直接传入对象,否则会抛出参数匹配异常。

内容的提问来源于stack exchange,提问作者Sammy Pawar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 04:27:14