Search code examples
javaspringspring-batchintegration-testingtesting-strategies

Testing of spring batch job causes unexpected results


I'm trying to create a simple backup job processing, to verify that my goals are correct.

I'm trying to use spring batch testing, for results verification.

PS My batch processing uses non default configuration provided by framework, because our job repository should use non default schema name.

The read step of my job is configured for lazy initialization with @StepScope annotation, it is needed because my job should have some parameters to query the database on reading step

This is the sample configuration that we're using It is in the root package, rest batch configs located in children packages

@Configuration
@Import({ApplicationHibernateConfiguration.class})
@ComponentScan
public class ApplicationBatchConfiguration extends DefaultBatchConfigurer {

    private static final String BATCH_PROCESSING_PREFIX = "BATCH_PROCESSING.BATCH_";
    private final DataSource dataSource;
    private final PlatformTransactionManager transactionManager;
    private final JobLauncher jobLauncher;
    private final JobRepository jobRepository;
    private final JobExplorer jobExplorer;

    @Autowired
    public GlobalLogisticsPortalBatchConfiguration(
            DataSource dataSource, PlatformTransactionManager transactionManager) throws Exception {
        this.dataSource = dataSource;
        this.transactionManager = transactionManager;
        this.jobRepository = createJobRepository();
        this.jobLauncher = createJobLauncher();
        this.jobExplorer = createJobExplorer();
    }


    @Override
    protected JobLauncher createJobLauncher() throws Exception {
        SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
        jobLauncher.setJobRepository(jobRepository);
        jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
        jobLauncher.afterPropertiesSet();
        return jobLauncher;
    }

    @Override
    protected JobRepository createJobRepository() throws Exception {
        JobRepositoryFactoryBean factoryBean = new JobRepositoryFactoryBean();
        factoryBean.setDatabaseType("DB2");
        factoryBean.setTablePrefix(BATCH_PROCESSING_PREFIX);
        factoryBean.setIsolationLevelForCreate("ISOLATION_REPEATABLE_READ");
        factoryBean.setDataSource(this.dataSource);
        factoryBean.setTransactionManager(this.transactionManager);
        factoryBean.afterPropertiesSet();
        return factoryBean.getObject();
    }

    @Override
    protected JobExplorer createJobExplorer() throws Exception {
        JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
        factoryBean.setDataSource(this.dataSource);
        factoryBean.setTablePrefix(BATCH_PROCESSING_PREFIX);
        factoryBean.afterPropertiesSet();
        return factoryBean.getObject();
    }

    @Override
    @Bean
    public JobRepository getJobRepository() {
        return jobRepository;
    }

    @Override
    public PlatformTransactionManager getTransactionManager() {
        return transactionManager;
    }

    @Override
    @Bean
    public JobLauncher getJobLauncher() {
        return jobLauncher;
    }

    @Override
    @Bean
    public JobExplorer getJobExplorer() {
        return jobExplorer;
    }

    @Bean
    public JobBuilderFactory jobBuilderFactory(JobRepository jobRepository) {
        return new JobBuilderFactory(jobRepository);
    }

    @Bean
    public StepBuilderFactory stepBuilderFactory(
            JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new StepBuilderFactory(jobRepository, transactionManager);
    }


}

Step that i'm trying to use looks like that:

    @Bean
    @StepScope
    public JdbcPagingItemReader<DomainObject> itemReader(
            @Value("#{jobParameters['id']}") String id) {
        JdbcPagingItemReader<DomainObject> reader = new JdbcPagingItemReader<>();
        reader.setDataSource(this.dataSource);
        reader.setFetchSize(10);
        Db2PagingQueryProvider nativeQueryProvider = new Db2PagingQueryProvider();
        nativeQueryProvider.setSelectClause("*");
        nativeQueryProvider.setFromClause("from SCHEMA.DOMAIN");
        nativeQueryProvider.setWhereClause("id = :id");
        Map<String, Object> params = new HashMap<>(1);
        params.put("id", id);
        reader.setRowMapper((rs, rowNum) -> {
            DomainObject element = new DomainObject();
            element.setId(rs.getString("ID"));
            return element;
        });
        reader.setParameterValues(params);
        reader.setQueryProvider(nativeQueryProvider);
        return reader;
    }

    @Bean
    public Step fetchDomain() throws Exception {
        return stepBuilderFactory.get("fetchDomain")
                .<HierarchyElement, HierarchyElement>chunk(10)
                .faultTolerant()
                .reader(itemReader(null))
                .writer(items -> items.forEach(System.out::println))
                .build();
    }

The actual job bean is currently configured to launch only single step

    @Bean
    public Job backupJob() throws Exception {
        return jobBuilderFactory.get("backupJob")
                .start(fetchHeid())
                .build();
    }

My Test code looks like following

@RunWith(SpringRunner.class)
@SpringBatchTest
@ContextConfiguration(classes = {ApplicationBatchConfiguration.class})
public class BackupJobConfigurationTest {
    @Autowired
    @Qualifier(value = "backupJob")
    public Job job;

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

    @Test
    public void flowTest() throws Exception {
        JobParameters parameters = new JobParametersBuilder()
                .addString("id", "124")
                .toJobParameters();

        JobExecution execution = jobLauncherTestUtils.launchJob(parameters);

        assertEquals(BatchStatuses.COMPLETED, execution.getExitStatus().getExitCode()); //failed
    }
}

I expect the exit code to be "COMPLETED" and get "UNKNOWN". Also I'm not sure that the code is actually getting even invoked because i don't see any outputs from the writer lambda.

The only output that i am seeing in test is

Aug 30, 2019 2:52:17 PM org.springframework.batch.core.launch.support.SimpleJobLauncher run
INFO: Job: [FlowJob: [name=backupJob]] launched with the following parameters: [{id=124}]

org.junit.ComparisonFailure: 
Expected :COMPLETED
Actual   :UNKNOWN

Solution

  • I am figured that out, first of all, i were need to remove SimpleAsyncTaskExecutor from my configuration to actually test the code in the same thread, then by more careful read of the reference I havve reconfigured my batch config, instead of extending BatchConfigurer directly I haveve configured it as a bean inside my configuration and add @EnableSpringBatch annotation

        @Bean
        public BatchConfigurer batchConfigurer() {
        return new DefaultBatchConfigurer() {
            @Override
            protected JobRepository createJobRepository() throws Exception {
                JobRepositoryFactoryBean factoryBean = new JobRepositoryFactoryBean();
                factoryBean.setDatabaseType("db2");
                factoryBean.setTablePrefix(BATCH_PROCESSING_PREFIX);
                factoryBean.setIsolationLevelForCreate("ISOLATION_REPEATABLE_READ");
                factoryBean.setDataSource(dataSource);
                factoryBean.setTransactionManager(transactionManager);
                factoryBean.afterPropertiesSet();
                return factoryBean.getObject();
            }
    
            @Override
            protected JobExplorer createJobExplorer() throws Exception {
                JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
                factoryBean.setDataSource(dataSource);
                factoryBean.setTablePrefix(BATCH_PROCESSING_PREFIX);
                factoryBean.afterPropertiesSet();
                return factoryBean.getObject();
            }
    
            @Override
            protected JobLauncher createJobLauncher() throws Exception {
                SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
                jobLauncher.setJobRepository(getJobRepository());
                //jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
                jobLauncher.afterPropertiesSet();
                return jobLauncher;
            }
        };
    }