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

JUnit5单元测试中如何Mock FileReader?遇文件找不到异常求助

问题根源

你测试失败的核心原因是Mockito无法直接拦截方法内部new FileReader(...)的构造调用。当前业务代码里直接在方法内实例化FileReader,测试代码里的Mockito.when(new FileReader(...))写法完全无效——Mockito默认只能Mock依赖注入的对象、类的成员变量,或者通过可替换的工厂方法获取的实例,直接new出来的对象不受Mock控制,所以测试时会真实去读取文件,自然抛出FileNotFoundException。

另外你的测试代码还有两个明显错误:

  • fileReader变量未定义,直接调用when(fileReader.read())会报空指针
  • Mock FileReader返回null会导致后续CSVReader初始化失败
解决方案

方案1:重构业务代码解耦依赖(推荐)

把文件读取的逻辑抽成一个可被Mock的方法,这样测试时可以替换这个方法的返回值,避免真实文件操作:

  1. 修改服务类,新增一个protected方法用于创建Reader:
public class CsvReaderService {
    // 原有成员变量、saveToDoDB方法等
    
    protected Reader createFileReader(String filePath) throws FileNotFoundException {
        return new FileReader(filePath);
    }

    public List<Map<String, String>> readTheCSVFile() {
        List<Map<String, String>> failedEntries = new ArrayList<>();
        try (CSVReader reader = new CSVReader(createFileReader(filePathDTO.getFilePath()))) {
            String[] nextLine;
            reader.readNext(); // 跳过表头
            while ((nextLine = reader.readNext()) != null) {
                saveToDoDB(nextLine, failedEntries);
            }
        } catch (IOException | CsvValidationException e) {
            LOG.error(e.getMessage(), e);
        }
        return failedEntries;
    }
}
  1. 编写测试用例,使用Mockito Spy来Mock这个protected方法:
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.io.StringReader;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;

@Test
public void readCSVFileTest() throws Exception {
    // 创建服务类的Spy对象,保留原有业务逻辑但可Mock指定方法
    CsvReaderService spyService = Mockito.spy(csvReaderService);
    
    // 模拟CSV内容,用StringReader替代真实文件读取
    String mockCsvContent = "header1,header2\nval1,val2\nval3,val4";
    Reader mockReader = new StringReader(mockCsvContent);
    
    // 拦截createFileReader方法,返回模拟的Reader
    Mockito.doReturn(mockReader).when(spyService).createFileReader(Mockito.anyString());
    
    // 调用测试方法
    List<Map<String, String>> failedEntries = spyService.readTheCSVFile();
    
    // 断言结果(根据你的业务逻辑调整,比如空列表表示全部保存成功)
    assertTrue(failedEntries.isEmpty());
}

方案2:使用Mockito构造函数Mock(无需修改业务代码)

如果不想重构业务代码,可以用Mockito 3.4+支持的构造函数Mock功能,直接拦截FileReader的创建:

@Test
public void readCSVFileTest() throws Exception {
    // 模拟CSV内容
    String mockCsvContent = "header1,header2\nval1,val2";
    Reader mockReader = new StringReader(mockCsvContent);
    
    // 拦截FileReader的所有构造调用,返回模拟的Reader
    try (Mockito.MockedConstruction<FileReader> mocked = Mockito.mockConstruction(FileReader.class,
            (mock, context) -> {
                // 当FileReader被创建时,让它的read方法返回模拟内容的字符
                Mockito.when(mock.read(Mockito.any(char[].class), Mockito.anyInt(), Mockito.anyInt()))
                        .thenAnswer(invocation -> {
                            char[] buffer = invocation.getArgument(0);
                            int offset = invocation.getArgument(1);
                            int length = invocation.getArgument(2);
                            return mockReader.read(buffer, offset, length);
                        });
            })) {
        // 调用测试方法
        List<Map<String, String>> failedEntries = csvReaderService.readTheCSVFile();
        
        // 断言逻辑
        assertTrue(failedEntries.isEmpty());
    }
}

额外提示

  • 测试时尽量用模拟的CSV内容(比如StringReader),避免依赖真实文件路径,保证测试的独立性
  • 如果saveToDoDB方法涉及数据库操作,记得也要Mock这个方法,避免测试时操作真实数据库

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:57:24