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

JUnit:Spring Batch中JDBC写入方法Mock测试失败排查

问题描述

开发了一个向SQL数据库写入数据的Spring Batch应用,数据库写入逻辑被包裹在功能开关条件中(代码如下)。编写单元测试时,希望Mock数据库写入操作并验证功能开关逻辑,但测试始终失败,提示jdbcBatchItemWriter.write()方法从未被调用。尝试添加doNothing().when(jdbcBatchItemWriter).write(any(List.class));也未解决问题,寻求可行方案。

业务代码(TreatmentWriter.java)

public class TreatmentWriter implements ItemWriter<TreatmentData> {

    JdbcBatchItemWriter<TreatmentData> jdbcBatchItemWriter;

    @Autowired
    @Qualifier("dataSource")
    private DataSource dataSource;

    @Autowired
    @Qualifier("shawQuery")
    private String shawQuery;

    @Autowired
    FeatureManager featureManager;

    @Override
    public void write(List<? extends TreatmentData> processedData) {
        for (TreatmentData data : processedData) {
            log.debug("Writing processed data: " + data.getTreatment());
        }
        try {
            if (featureManager.isActive(FeaturesToToggle.FEATURE_FLAG)) {
                log.debug("Query to update staging table: " + shawQuery);
                jdbcBatchItemWriter = new JdbcBatchItemWriter<>();
                jdbcBatchItemWriter.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
                jdbcBatchItemWriter.setDataSource(dataSource);
                jdbcBatchItemWriter.setSql(shawQuery);
                jdbcBatchItemWriter.write(processedData);
            } else {
                log.info("feature flag is disabled");
            }
        } catch (Exception ex) {
            log.error("Error writing to DB: {}", ex);
        }
    }
}

测试代码(TreatmentWriterTest.java)

@ExtendWith(MockitoExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ActiveProfiles("test")
@ContextConfiguration(classes = { TreatmentWriter.class})
public class TreatmentWriterTest {

    private MemoryAppender memoryAppender;

    @InjectMocks
    TreatmentWriter treatmentWriter;

    @Mock
    private FeatureManager featureManager;

    @Spy
    private JdbcBatchItemWriter<TreatmentData> jdbcBatchItemWriter;

    @Mock
    private SecretsBeans secretsBeans;

    @Mock
    private DataSource shawDataSourceCollectEtl;

    @BeforeEach
    public void init() throws NoSuchFieldException, IllegalAccessException {
        Logger logger = (Logger) LoggerFactory.getLogger("com.ally.cr");
        memoryAppender = new MemoryAppender();
        memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
        logger.setLevel(Level.DEBUG);
        logger.addAppender(memoryAppender);
        memoryAppender.start();
    }

    @Test
    public void UT_whenFeatureFlagIsOn_writeToShaw() throws Exception {
        mockFeatureFlag(true); //setting flag to true

        Field shawQuery;
        shawQuery = treatmentWriter.getClass().getDeclaredField("shawQuery");
        shawQuery.setAccessible(true);
        shawQuery.set(treatmentWriter, "");

        TreatmentData treatmentData = createTreatmentData(); //generating test data
    
        treatmentWriter.write(Arrays.asList(treatmentData));
        assertThat(memoryAppender.search("Query to update Shaw staging table: ", Level.INFO).size()).isEqualTo(1);
        verify(jdbcBatchItemWriter).write(any(List.class));
    }
}

问题根源

测试失败的核心原因是:在TreatmentWriter的write方法中,每次功能开关开启时,都会新建一个JdbcBatchItemWriter实例,并覆盖类成员变量jdbcBatchItemWriter。而测试代码中通过@Spy创建的是类初始的成员变量实例,实际执行时调用的是新创建的对象,因此verify的是未被使用过的初始实例,自然会提示方法从未被调用。


解决方案

需要重构TreatmentWriter,避免在write方法中重复创建JdbcBatchItemWriter实例,改用依赖注入的方式管理该对象,并提前完成初始化配置。

重构后的业务代码(推荐构造注入)

构造注入更利于依赖管理和单元测试,同时将JdbcBatchItemWriter的初始化逻辑移到构造方法中:

public class TreatmentWriter implements ItemWriter<TreatmentData> {

    private final JdbcBatchItemWriter<TreatmentData> jdbcBatchItemWriter;
    private final DataSource dataSource;
    private final String shawQuery;
    private final FeatureManager featureManager;

    @Autowired
    public TreatmentWriter(JdbcBatchItemWriter<TreatmentData> jdbcBatchItemWriter,
                          @Qualifier("dataSource") DataSource dataSource,
                          @Qualifier("shawQuery") String shawQuery,
                          FeatureManager featureManager) {
        this.jdbcBatchItemWriter = jdbcBatchItemWriter;
        this.dataSource = dataSource;
        this.shawQuery = shawQuery;
        this.featureManager = featureManager;
        
        // 初始化JdbcBatchItemWriter配置
        this.jdbcBatchItemWriter.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
        this.jdbcBatchItemWriter.setDataSource(dataSource);
        this.jdbcBatchItemWriter.setSql(shawQuery);
        
        // 必须调用afterPropertiesSet完成初始化(Spring Batch要求)
        try {
            this.jdbcBatchItemWriter.afterPropertiesSet();
        } catch (Exception e) {
            throw new RuntimeException("初始化JdbcBatchItemWriter失败", e);
        }
    }

    @Override
    public void write(List<? extends TreatmentData> processedData) {
        for (TreatmentData data : processedData) {
            log.debug("Writing processed data: " + data.getTreatment());
        }
        try {
            if (featureManager.isActive(FeaturesToToggle.FEATURE_FLAG)) {
                log.debug("Query to update staging table: " + shawQuery);
                jdbcBatchItemWriter.write(processedData);
            } else {
                log.info("feature flag is disabled");
            }
        } catch (Exception ex) {
            log.error("Error writing to DB: {}", ex);
        }
    }
}

调整后的测试代码

现在JdbcBatchItemWriter是注入的实例,测试时可以直接Mock,无需担心被覆盖:

@ExtendWith(MockitoExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ActiveProfiles("test")
public class TreatmentWriterTest {

    private MemoryAppender memoryAppender;

    private TreatmentWriter treatmentWriter;

    @Mock
    private FeatureManager featureManager;

    @Mock
    private JdbcBatchItemWriter<TreatmentData> jdbcBatchItemWriter;

    @Mock
    @Qualifier("dataSource")
    private DataSource dataSource;

    @BeforeEach
    public void init() {
        // 初始化日志Appender
        Logger logger = (Logger) LoggerFactory.getLogger("com.ally.cr");
        memoryAppender = new MemoryAppender();
        memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
        logger.setLevel(Level.DEBUG);
        logger.addAppender(memoryAppender);
        memoryAppender.start();

        // 手动创建TreatmentWriter实例,传入所有Mock对象
        treatmentWriter = new TreatmentWriter(jdbcBatchItemWriter, dataSource, "", featureManager);
    }

    @Test
    public void UT_whenFeatureFlagIsOn_writeToShaw() throws Exception {
        // 模拟功能开关开启
        when(featureManager.isActive(FeaturesToToggle.FEATURE_FLAG)).thenReturn(true);

        TreatmentData treatmentData = createTreatmentData();
        treatmentWriter.write(Arrays.asList(treatmentData));

        // 验证日志输出
        assertThat(memoryAppender.search("Query to update staging table: ", Level.DEBUG).size()).isEqualTo(1);
        // 验证write方法被调用
        verify(jdbcBatchItemWriter).write(any(List.class));
    }

    @Test
    public void UT_whenFeatureFlagIsOff_doNotWrite() throws Exception {
        // 模拟功能开关关闭
        when(featureManager.isActive(FeaturesToToggle.FEATURE_FLAG)).thenReturn(false);

        TreatmentData treatmentData = createTreatmentData();
        treatmentWriter.write(Arrays.asList(treatmentData));

        // 验证日志输出
        assertThat(memoryAppender.search("feature flag is disabled", Level.INFO).size()).isEqualTo(1);
        // 验证write方法从未被调用
        verify(jdbcBatchItemWriter, never()).write(any(List.class));
    }

    private TreatmentData createTreatmentData() {
        TreatmentData data = new TreatmentData();
        data.setTreatment("test-treatment");
        return data;
    }
}

内容的提问来源于stack exchange,提问作者shoaib-jpeg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 15:10:18