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的方法,这样测试时可以替换这个方法的返回值,避免真实文件操作:
- 修改服务类,新增一个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; } }
- 编写测试用例,使用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
相关产品推荐
相关产品推荐

