如何用JUnit5+Mockito测试Spring Boot抽象类CsvService的readFromCsv方法
测试抽象类CsvService的readFromCsv方法方案
你的问题核心是测试时未隔离实际文件读取依赖,且未正确mock抽象方法行为,导致代码走真实文件读取逻辑。以下是两种可行的测试方案:
方案一:重构抽象类,抽取文件读取方法(推荐)
先对CsvService做轻量重构,把文件读取逻辑抽成受保护方法,方便测试时mock:
public abstract class CsvService<T> { public List<T> readFromCsv() throws IOException { String fileName = getFileName(); List<String> columns = getColumns(); // 调用抽取的文件读取方法 try (BufferedReader reader = getFileReader(fileName)) { List<T> data = new ArrayList<>(); // 跳过表头(根据实际逻辑调整) reader.readLine(); String row; while ((row = reader.readLine()) != null) { String[] rowData = row.split(","); data.add(getData(rowData)); } return data; } } // 抽取文件读取方法,供测试mock protected BufferedReader getFileReader(String fileName) throws IOException { return new BufferedReader(new FileReader(fileName)); } protected abstract String getFileName(); protected abstract List<String> getColumns(); protected abstract T getData(String[] row); }
然后编写JUnit5+Mockito测试:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import java.io.BufferedReader; import java.io.StringReader; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class CsvServiceTest { // 创建抽象类的mock实例,指定调用真实的readFromCsv方法 private CsvService<TestEntity> csvService = mock(CsvService.class, CALLS_REAL_METHODS); @Test void readFromCsv_shouldReturnMockData() throws Exception { // 1. Mock三个抽象方法的返回 when(csvService.getFileName()).thenReturn("dummy.csv"); when(csvService.getColumns()).thenReturn(List.of("id", "name")); // 模拟getData将CSV行转成实体 when(csvService.getData(any(String[].class))).thenAnswer(invocation -> { String[] row = invocation.getArgument(0); return new TestEntity(row[0], row[1]); }); // 2. Mock文件读取方法,返回测试用的CSV内容 String testCsvContent = "id,name\n1,test_user_1\n2,test_user_2"; BufferedReader mockReader = new BufferedReader(new StringReader(testCsvContent)); when(csvService.getFileReader(anyString())).thenReturn(mockReader); // 3. 执行测试方法 List<TestEntity> result = csvService.readFromCsv(); // 4. 验证结果 assertEquals(2, result.size()); assertEquals("test_user_1", result.get(0).getName()); assertEquals("2", result.get(1).getId()); } // 测试用实体类,替换成你的实际业务实体 static class TestEntity { private String id; private String name; public TestEntity(String id, String name) { this.id = id; this.name = name; } public String getId() { return id; } public String getName() { return name; } } }
方案二:Spy实际子类,隔离文件读取
如果需要基于实际子类AirportService测试,可通过Spy包装子类,mock掉文件读取和抽象方法的真实实现:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import java.io.BufferedReader; import java.io.StringReader; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.doReturn; @ExtendWith(MockitoExtension.class) public class AirportServiceTest { // Spy实际子类,保留真实逻辑但可指定方法mock @Spy private AirportService airportService = new AirportService(); @Test void readFromCsv_shouldReturnMockAirportData() throws Exception { // Mock getFileName,避免读取真实文件路径 doReturn("dummy_airport.csv").when(airportService).getFileName(); // Mock文件读取,返回测试CSV内容 String testAirportCsv = "code,city\nPEK,Beijing\nSHA,Shanghai"; BufferedReader mockReader = new BufferedReader(new StringReader(testAirportCsv)); doReturn(mockReader).when(airportService).getFileReader(anyString()); // 执行测试 List<Airport> result = airportService.readFromCsv(); // 断言验证 assertEquals(2, result.size()); assertEquals("PEK", result.get(0).getCode()); } }
关键注意点
- 使用
mock(CsvService.class, CALLS_REAL_METHODS)可让抽象类的真实readFromCsv逻辑执行,同时mock抽象方法的返回 - 必须隔离文件读取依赖:要么重构抽取方法,要么mock文件读取相关IO操作,绝对不能让测试代码访问真实文件
- 三个抽象方法的mock要匹配测试数据格式,确保
readFromCsv的解析逻辑能正确映射数据
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

