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
相关产品推荐
相关产品推荐

