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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 23:30:25