BreenDeen
BreenDeen

Reputation: 732

How to successfullky load the JobLauncherTestUtils class for a Spring Task for a Spring Batch JUnit 5 Test

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

Answers (1)

Mahmoud Ben Hassine
Mahmoud Ben Hassine

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

Related Questions