3

I am using Spring boot and Spring batch. I have defined more than one job.

I am trying to build junit to test specific task within a job.

Therefor I am using the JobLauncherTestUtils library.

When I run my test case I always get NoUniqueBeanDefinitionException.

This is my test class:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {BatchConfiguration.class})
public class ProcessFileJobTest  {

    @Configuration
    @EnableBatchProcessing
    static class TestConfig {
        @Autowired

        private JobBuilderFactory jobBuilder;
        @Autowired
        private StepBuilderFactory stepBuilder;

        @Bean
        public JobLauncherTestUtils jobLauncherTestUtils() {
            JobLauncherTestUtils jobLauncherTestUtils = new JobLauncherTestUtils();
            jobLauncherTestUtils.setJob(jobUnderTest());
            return jobLauncherTestUtils;
        }


        @Bean
        public Job jobUnderTest() {
            return jobBuilder.get("job-under-test")
                    .start(processIdFileStep())
                    .build();
        }


        @Bean
        public Step processIdFileStep() {
            return stepBuilder.get("processIdFileStep")
                    .<PushItemDTO, PushItemDTO>chunk(1) //important to be one in this case to commit after every line read
                    .reader(reader(null))

                    .processor(processor(null, null, null, null))
                    .writer(writer())

                            //     .faultTolerant()
                            //   .skipLimit(10) //default is set to 0
                            //     .skip(MySQLIntegrityConstraintViolationException.class)
                    .build();
        }


        @Bean
        @Scope(value = "step", proxyMode = ScopedProxyMode.INTERFACES)
        public ItemStreamReader<PushItemDTO> reader(@Value("#{jobExecutionContext[filePath]}") String filePath) {
            ...
            return itemReader;
        }

        @Bean
        @Scope(value = "step", proxyMode = ScopedProxyMode.INTERFACES)
        public ItemProcessor<PushItemDTO, PushItemDTO> processor(@Value("#{jobParameters[pushMessage]}") String pushMessage,
                                                                 @Value("#{jobParameters[jobId]}") String jobId,
                                                                 @Value("#{jobParameters[taskId]}") String taskId,
                                                                 @Value("#{jobParameters[refId]}") String refId)
        {
            return new PushItemProcessor(pushMessage,jobId,taskId,refId);
        }

        @Bean
        public LineMapper<PushItemDTO> lineMapper() {
            DefaultLineMapper<PushItemDTO> lineMapper = new DefaultLineMapper<PushItemDTO>();
           ...
            return lineMapper;
        }

        @Bean
        public ItemWriter writer() {
            return new someWriter();
        }
    }


    @Autowired
    protected JobLauncher jobLauncher;

    @Autowired
    JobLauncherTestUtils jobLauncherTestUtils;



    @Test
    public void processIdFileStepTest1() throws Exception {
        JobParameters jobParameters = new JobParametersBuilder().addString("filePath", "C:\\etc\\files\\2015_02_02").toJobParameters();
        JobExecution jobExecution = jobLauncherTestUtils.launchStep("processIdFileStep",jobParameters);

    }

and thats the exception:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.batch.core.Job] is defined: expected single matching bean but found 3: jobUnderTest,executeToolJob,processFileJob

Any idea? Thanks.

added BatchConfiguration class:

package com.mycompany.notification_processor_service.batch.config;

import com.mycompany.notification_processor_service.common.config.CommonConfiguration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;


@ComponentScan("com.mycompany.notification_processor_service.batch")
@PropertySource("classpath:application.properties")
@Configuration
@Import({CommonConfiguration.class})
@ImportResource({"classpath:applicationContext-pushExecuterService.xml"/*,"classpath:si/integration-context.xml"*/})
public class BatchConfiguration {

    @Value("${database.driver}")
    private String databaseDriver;
    @Value("${database.url}")
    private String databaseUrl;
    @Value("${database.username}")
    private String databaseUsername;
    @Value("${database.password}")
    private String databasePassword;



    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(databaseDriver);
        dataSource.setUrl(databaseUrl);
        dataSource.setUsername(databaseUsername);
        dataSource.setPassword(databasePassword);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

}

and this is CommonConfiguration

@ComponentScan("com.mycompany.notification_processor_service")
@Configuration
@EnableJpaRepositories(basePackages = {"com.mycompany.notification_processor_service.common.repository.jpa"})
@EnableCouchbaseRepositories(basePackages = {"com.mycompany.notification_processor_service.common.repository.couchbase"})
@EntityScan({"com.mycompany.notification_processor_service"})
@EnableAutoConfiguration
@EnableTransactionManagement
@EnableAsync
public class CommonConfiguration {

}
rayman
  • 20,786
  • 45
  • 148
  • 246

4 Answers4

6

I had the same issue and the easier way is injecting in the setter of JobLauncherTestUtils like Mariusz explained in Jira of Spring:

@Bean
public JobLauncherTestUtils getJobLauncherTestUtils() {

    return new JobLauncherTestUtils() {
        @Override
        @Autowired
        public void setJob(@Qualifier("ncsvImportJob") Job job) {
            super.setJob(job);
        }
    };
}
Pau
  • 14,917
  • 14
  • 67
  • 94
0

So I see the jobUnderTest bean. Somewhere in all those imports, you're importing the two other jobs as well. I see your BatchConfiguration class imports other stuff as well as you having component scanning turned on. Carefully trace through all your configurations. Something is picking up the definitions for those beans.

Michael Minella
  • 20,843
  • 4
  • 55
  • 67
0

I also ran into this issue and couldn't have JobLauncherTestUtils to work properly. It might be caused by this issue

I ended up autowiring the SimpleJobLauncher and my Job into the unit test, and simply

launcher.run(importAccountingDetailJob, params);
lejeune.n
  • 312
  • 2
  • 11
0

An old post, but i thought of providing my solution as well.

In this case i am automatically registering a JobLauncherTestUtils per job

@Configuration
public class TestConfig {

    private static final Logger logger = LoggerFactory.getLogger(TestConfig.class);

    @Autowired
    private AbstractAutowireCapableBeanFactory beanFactory;

    @Autowired
    private List<Job> jobs;

    @PostConstruct
    public void registerServices() {
       jobs.forEach(j->{
         JobLauncherTestUtils u = create(j);
         final String name = j.getName()+"TestUtils"
         beanFactory.registerSingleton(name,u);
           beanFactory.autowireBean(u);
           logger.info("Registered JobLauncherTestUtils {}",name);

       });

    }

    private JobLauncherTestUtils create(final Job j) {
        return new MyJobLauncherTestUtils(j);
    }

    private static class MyJobLauncherTestUtils extends JobLauncherTestUtils {


        MyJobLauncherTestUtils(Job j) {
            this.setJob(j);
        }

        @Override // to remove @Autowire from base class
        public void setJob(Job job) {
            super.setJob(job);
        }
    }
}
raphaëλ
  • 6,393
  • 2
  • 29
  • 35