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

Spring Batch配置测试:Mock/注入DataSource解决依赖缺失报错

问题描述

我需要测试自定义的JobLDSExecutorConfiguration类,编写了如下测试方法:

@Test
void testStep1() {
    JobExecution jobExecution = jobLauncherTestUtils.launchStep("loadLds");
     Assert.assertEquals(new ExitStatus("COMPLETED").getExitCode() ,jobExecution.getExitStatus().getExitCode());
}

但运行测试时出现报错:

Error creating bean with name 'jobLDSExecutorConfiguration': Unsatisfied dependency expressed through constructor parameter 3; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

现咨询是否可以Mock或注入DataSource,以及如何完成该类的测试。相关业务配置代码如下:

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.Order;
import org.springframework.batch.item.database.builder.JdbcPagingItemReaderBuilder;
import org.springframework.batch.item.database.support.OraclePagingQueryProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;


@Configuration
@Slf4j
@RequiredArgsConstructor
@EnableScheduling
@EnableBatchProcessing
public class JobLDSExecutorConfiguration {
    private final KafkaTemplate<String, String> kafkaTemplate;
    private final StepBuilderFactory stepBuilderFactory;
    private final JobBuilderFactory jobBuilderFactory;
    private final DataSource dataSource;
    private final JobRepository jobRepository;
    private static final int CHUNK_SIZE = 1;
    @Value("${kafka.rrss-correspondances-services-consommateur.topic}")
    private String topic;
    @Value("${task.thread.limit}")
    private int limteThread;
    @Value("${task.size}")
    private int size;
    @Value("${task.pageSize}")
    private int pageSize;

    private ChunkExecutionListener chunkListener() {
        return new ChunkExecutionListener();
    }

    @Bean
    public ItemReader<LdsOds> ldstJdbcItemReader() {
        return new JdbcPagingItemReaderBuilder<LdsOds>()
                .name("pagingItemReaderLDS")
                .fetchSize(size)
                .dataSource(dataSource)
                .pageSize(pageSize)
                .queryProvider(queryProvider())
                .rowMapper(new LdsOdsRowMapper(TypeDonnee.LDS))
                .build();
    }

    private OraclePagingQueryProvider queryProvider() {
        OraclePagingQueryProvider provider =
                new OraclePagingQueryProvider();
        provider.setSelectClause(SQL_SELECT_LDS);
        provider.setFromClause(SQL_FROM_LDS);
        Map<String, Order> sortKeys = new HashMap<>(1);
        sortKeys.put("SERVICE_DELIVERY_LOCATION_ID", Order.ASCENDING);
        provider.setSortKeys(sortKeys);
        return provider;
    }

    @Bean
    public ItemWriter<LdsOds> kafkaLDSItemWriter() {
        return items -> {
            for (LdsOds item : items) {
                kafkaTemplate.send(topic, item.getId().toString(), new ObjectMapper().writeValueAsString(item));
            }
        };
    }

    @Bean
    public TaskExecutor taskLDSExecutor() {
        SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
        taskExecutor.setConcurrencyLimit(limteThread);
        return taskExecutor;
    }

    @Bean
    public Step loadLds() {
        return stepBuilderFactory.get("loadLds")
                .<LdsOds, LdsOds>chunk(CHUNK_SIZE)
                .reader(ldstJdbcItemReader())
                .writer(kafkaLDSItemWriter())
                .taskExecutor(taskLDSExecutor())
                .listener(chunkListener())
                .throttleLimit(limteThread)
                .build();
    }

    @Bean
    public JobExecutionListener jobLDSListener() {
        return new JobCompletionNotificationListener();
    }

    @Bean(name = "jobLDSExtraction")
    public Job jobLDSExtraction() {
        return jobBuilderFactory.get("jobLDSExtraction")
                .incrementer(new RunIdIncrementer())
                .listener(jobLDSListener())
                .start(loadLds())
                .build();
    }

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(15);
        taskExecutor.setMaxPoolSize(20);
        taskExecutor.setQueueCapacity(30);
        return taskExecutor;
    }

    @Bean
    public JobLauncher jobLauncher() throws Exception {
        SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
        jobLauncher.setTaskExecutor(taskExecutor()); // Or below line
        jobLauncher.setJobRepository(jobRepository);
        jobLauncher.afterPropertiesSet();
        return jobLauncher;
    }

    @Scheduled(fixedRate = 10000)
    public void executerMajIdentifiantLDS() {
        Map<String, JobParameter> confMap = new HashMap<>();
        confMap.put("time", new JobParameter(System.currentTimeMillis()));
        JobParameters jobParameters = new JobParameters(confMap);
        try {
            jobLauncher().run(jobLDSExtraction(), jobParameters);
        } catch (Exception ex) {
            log.error("Erreur pendant l'execution du jobLDSExtraction {}", ex.getMessage());
        }
    }
}
解决方案

完全可以通过Mock或注入DataSource完成测试,下面提供两种可行方案:

方案一:使用Mock模拟依赖(适合单元测试)

通过@MockBean模拟DataSource、KafkaTemplate等外部依赖,搭配@SpringBatchTest简化Spring Batch测试配置:

import org.junit.jupiter.api.Test;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.kafka.core.KafkaTemplate;

import javax.sql.DataSource;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBatchTest
@SpringBootTest(classes = JobLDSExecutorConfiguration.class)
class JobLDSExecutorConfigurationTest {

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

    // 模拟外部依赖,避免真实连接
    @MockBean
    private DataSource dataSource;
    @MockBean
    private KafkaTemplate<String, String> kafkaTemplate;

    @Test
    void testStep1() throws Exception {
        JobExecution jobExecution = jobLauncherTestUtils.launchStep("loadLds");
        assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode());
    }
}

补充说明:

  • @SpringBatchTest会自动配置JobLauncherTestUtils等测试工具类,无需手动初始化。
  • 如果需要验证KafkaTemplate的调用行为,可以添加Mockito的验证逻辑:
    import static org.mockito.Mockito.verify;
    import static org.mockito.ArgumentMatchers.anyString;
    
    // 在测试方法中添加
    verify(kafkaTemplate).send(anyString(), anyString(), anyString());
    

方案二:使用嵌入式数据库(适合集成测试)

如果需要验证真实的JDBC读取逻辑,可以配置H2嵌入式数据库,并预先插入测试数据:

1. 添加H2依赖(pom.xml)

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>

2. 编写集成测试类

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.kafka.core.KafkaTemplate;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBatchTest
@SpringBootTest(classes = {JobLDSExecutorConfiguration.class, TestDataSourceConfig.class})
class JobLDSExecutorConfigurationIntegrationTest {

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @MockBean
    private KafkaTemplate<String, String> kafkaTemplate;

    @BeforeEach
    void setUp() {
        // 创建与生产环境匹配的测试表并插入测试数据
        jdbcTemplate.execute("CREATE TABLE SERVICE_DELIVERY_LOCATION (SERVICE_DELIVERY_LOCATION_ID INT, ...)");
        jdbcTemplate.update("INSERT INTO SERVICE_DELIVERY_LOCATION VALUES (1, ...)");
    }

    @Test
    void testStep1() throws Exception {
        JobExecution jobExecution = jobLauncherTestUtils.launchStep("loadLds");
        assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode());
    }
}

// 测试用DataSource配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

import javax.sql.DataSource;

@Configuration
class TestDataSourceConfig {
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .build();
    }
}

补充说明:

  • 嵌入式数据库会在测试启动时创建,测试结束后自动销毁,无需手动清理。
  • 需确保测试表结构与生产环境Oracle表一致,避免SQL执行报错。

额外注意事项

  • 如果ChunkExecutionListener或JobCompletionNotificationListener有额外依赖,也需要用@MockBean模拟或提供测试Bean。
  • 可以通过@TestPropertySource覆盖配置值,比如调整线程数、Kafka主题等:
    @TestPropertySource(properties = {
        "task.thread.limit=2",
        "kafka.rrss-correspondances-services-consommateur.topic=test-topic"
    })
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 02:25:16