Search code examples
springspring-batchspring-test

No qualifying bean of type 'org.springframework.batch.core.configuration annotation.JobBuilderFactory' available: in Junit


I went through below few links already, but did not turn to be working, I'm using Spring Boot v2.7.1 and Batch and has below code and using junit 5.

Error:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.batch.core.configuration.annotation.JobBuilderFactory' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1799) ~[spring-beans-5.3.20.jar:5.3.20]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1355) ~[spring-beans-5.3.20.jar:5.3.20]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309) ~[spring-beans-5.3.20.jar:5.3.20]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:656) ~[spring-beans-5.3.20.jar:5.3.20]
    ... 113 common frames omitted

JUnit

@RunWith(SpringRunner.class)
@SpringBatchTest
@EnableAutoConfiguration
@ContextConfiguration(classes = { JobConfiguration.class })
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, 
  DirtiesContextTestExecutionListener.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class XmlFileOutputApplicationTests {

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

    @Test
    public void contextLoads() throws Exception {
        JobParameters jobParameters = new JobParametersBuilder()
                .addString("JobId", String.valueOf(System.currentTimeMillis())).addDate("date", new Date())
                .addLong("time", System.currentTimeMillis()).toJobParameters();

        JobExecution jobExecution = jobLauncherTestUtils.launchJob(jobParameters);
        System.out.println(jobExecution.getExitStatus());
    }
}

JobConfig.java

@Configuration
public class JobConfiguration {
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    
    @Autowired
    private DataSource dataSource;
    
    @Bean
    public JdbcPagingItemReader<Customer> customerPagingItemReader(){
        // Sort Keys
        Map<String, Order> sortKeys = new HashMap<>();
        sortKeys.put("id", Order.ASCENDING);

        // MySQL implementation of a PagingQueryProvider using database specific features.
        MySqlPagingQueryProvider queryProvider = new MySqlPagingQueryProvider();
        queryProvider.setSelectClause("id, firstName, lastName, birthdate");
        queryProvider.setFromClause("from customer");
        queryProvider.setSortKeys(sortKeys);

        return new JdbcPagingItemReaderBuilder<Customer>()
                .name("customerPagingItemReader")
                .dataSource(this.dataSource)
                .fetchSize(1000)
                .rowMapper(new CustomerRowMapper())
                .queryProvider(queryProvider)
                .build();
    }
    
    
    @Bean
    public StaxEventItemWriter<Customer> customerItemWriter() throws Exception{
        String customerOutputPath = File.createTempFile("customerOutput", ".out").getAbsolutePath();
        System.out.println(">> Output Path = "+customerOutputPath);

        Map<String, Class> aliases = new HashMap<>();
        aliases.put("customer", Customer.class);

        XStreamMarshaller marshaller = new XStreamMarshaller();
        marshaller.setAliases(aliases);

        return new StaxEventItemWriterBuilder<Customer>()
                .name("customerItemWriter")
                .rootTagName("customers")
                .marshaller(marshaller)
                .resource(new FileSystemResource(customerOutputPath))
                .build();
    }
    
    
    @Bean
    public Step step1() throws Exception {
        return stepBuilderFactory.get("step1")
                .<Customer, Customer>chunk(100)
                .reader(customerPagingItemReader())
                .writer(customerItemWriter())
                .build();
    }
    
    @Bean
    public Job job() throws Exception {
        return jobBuilderFactory.get("job")
                .start(step1())
                .build();
    }
}

MainApp.java

@SpringBootApplication
@EnableBatchProcessing
public class XmlFileOutputApplication implements CommandLineRunner{
    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private Job job;
    
    public static void main(String[] args) {
        SpringApplication.run(XmlFileOutputApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        JobParameters jobParameters = new JobParametersBuilder()
                .addString("JobId", String.valueOf(System.currentTimeMillis()))
                .addDate("date", new Date())
                .addLong("time",System.currentTimeMillis()).toJobParameters();
        
        JobExecution execution = jobLauncher.run(job, jobParameters);
        System.out.println("STATUS :: "+execution.getStatus());
    }
}

Solution

  • Both JobBuilderFactory and StepBuilderFactory beans are defined by @EnableBatchProcessing.

    But now as you explicitly configure the spring test to load the context from the JobConfiguration , the XmlFileOutputApplication will never be picked up as the configuration class and hence the @EnableBatchProcessing annotated on it does not have any effect.

    The easy way to fix it is to make sure you include @EnableBatchProcessing on any configuration classes that the spring test will load. You can do it by :

    @ContextConfiguration(classes = { JobConfiguration.class })
    @EnableBatchProcessing
    public class XmlFileOutputApplicationTests {
    
    }
    

    or annotating @EnableBatchProcessing on JobConfiguration :

    @ContextConfiguration(classes = { JobConfiguration.class })
    public class XmlFileOutputApplicationTests {
    
    }
    
    @Configuration
    @EnableBatchProcessing
    public class JobConfiguration {
    
    }