Reputation: 732
I have this seemingly straightforward configuration for a spring batch test. However, the test fails to load the context. The error tells me there is an unresolvable circular reference related to the datasource. If I remove the datasource, the error changes that a datasource has not been defined. All I am trying to achieve is to be able to load the JobLauncherTestUtils and then launch a job for testing the steps. It seems simple enough but it is frustratingly difficult to load a simple job for testing. I am running a spring boot 2 on Java 12 and spring 5 and spring batch 2.2.0.RELEASE and JUnit 5 (Jupiter). What's the problem with the configuration? What changes do I need to make. I thought you would not necessarily have to specify a datasource configuration, but it simply does not come close to loading without configuring one. Here is my configuration. I'd really appreciate some help with figuring out what's wrong with my configuration. My objective is simply to successfully autowire the JobLauncherTestUtils class.
like
class BatchTest {
@Autowired
JobLauncherTestUtils jobLauncherTestUtils;
}
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.batch.core.Job;
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.support.JobRepositoryFactoryBean;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
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.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBatchTest
@ContextConfiguration(classes = {BatchTest.BatchConfiguration.class,
BatchAutoConfiguration.class})
@TestPropertySource(properties = "debug=true")
public class BatchTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Test
public void test() {
this.jobLauncherTestUtils.launchStep("orderProcessingJob");
//assertions
}
@Configuration
@EnableBatchProcessing
public static class BatchConfiguration {
private JobBuilderFactory jobBuilderFactory;
@Bean(name="jobLauncherTestUtils")
public JobLauncherTestUtils jobLauncherTestUtils() throws Exception {
JobLauncherTestUtils jl = new JobLauncherTestUtils();
jl.setJobLauncher(jobLauncher());
jl.setJob(orderProcessorJob());
jl.setJobRepository(jobRepository().getObject());
return jl;
}
@Bean
public JobRepositoryFactoryBean jobRepository() {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource());
return jobRepositoryFactoryBean;
}
@Bean
public Job orderProcessorJob() {
return jobBuilderFactory.get("orderProcessingJob")
.incrementer(new RunIdIncrementer())
.flow(orderIntakeStep())
.end()
.build();
}
private Step orderIntakeStep() {
// code for order Intake
}
@Bean
public JobLauncher jobLauncher() throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository().getObject());
return jobLauncher;
}
private StepBuilderFactory stepBuilderFactory;
@Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder().build();
}
@Autowired
public BatchConfiguration(JobBuilderFactory jobBuilderFactory ,
StepBuilderFactory stepBuilderFactory) {
this.jobBuilderFactory = jobBuilderFactory;
this.stepBuilderFactory = stepBuilderFactory;
}
}
}
Here is the core piece of the stacktrace I am seeing:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'batchTest.BatchConfiguration':
Unsatisfied dependency expressed through constructor parameter 0; nested exception
is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration':
Unsatisfied dependency expressed through field 'dataSource'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'batchTest.BatchConfiguration': Requested bean is currently in creation: Is there an unresolvable circular reference?
Upvotes: 3
Views: 10057
Reputation: 31730
Unsatisfied dependency expressed through field 'dataSource'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'batchTest.BatchConfiguration': Requested bean is currently in creation: Is there an unresolvable circular reference?
The data source you are trying to inject in BatchConfiguration#jobRepository()
is being created in the same class (dataSource()
method), hence the error. You need to extract your data source configuration in a separate class, something like:
@Configuration
public static class DataSourceConfig {
@Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder().build();
}
}
Then import it in your BatchConfiguration
:
@Configuration
@Import(DataSourceConfig.class)
@EnableBatchProcessing
public static class BatchConfiguration {
// ..
@Bean
public JobRepositoryFactoryBean jobRepository(DataSource dataSource) {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
return jobRepositoryFactoryBean;
}
// ..
}
I would recommend separating infrastructure beans from business logic beans. That said, since you are using @EnableBatchProcessing
, there is no need to configure the job repository, job launcher, etc yourself. The goal of this annotation is to provide those infrastructure beans for you, see its javadoc.
My objective is simply to successfully autowire the JobLauncherTestUtils class.
Since you are using @SpringBatchTest
, there is no need to create the JobLauncherTestUtils
manually (in your jobLauncherTestUtils()
method), the goal of this annotation is to import it for you, see its Javadoc. Here is a typical usage:
@RunWith(SpringRunner.class)
@SpringBatchTest
@ContextConfiguration(classes = MyBatchJobConfiguration.class)
public class MyBatchJobTests {
@@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@@Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
@Before
public void clearJobExecutions() {
this.jobRepositoryTestUtils.removeJobExecutions();
}
@Test
public void testMyJob() throws Exception {
// given
JobParameters jobParameters = this.jobLauncherTestUtils.getUniqueJobParameters();
// when
JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters);
// then
Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
}
}
Upvotes: 2