1

I am having trouble loading application context from junit tests for my spring batch tests. I referred spring documentation https://docs.spring.io/spring-batch/trunk/reference/html/testing.html as well as every available information I could find on web, but still cannot get a simple junit test work.

I am using annotations to load the app context, code below. My aim is to be able to test individual steps.

I also cloned some examples from web, but when run in my local machine, they give me same error- unable to load application context... this got me thinking on how these tests are suppose to run? Run As -> junit tests is how all the unit tests run... isn't it?

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {BatchProcessingConfiguration.class, RestTemplateConfig.class, SftpConfig.class, DbConfig.class, RuntimeSessionFactoryLocator.class}, 
                      loader = AnnotationConfigContextLoader.class)
public class BatchProcessingJobApplicationTests extends AbstractTransactionalJUnit4SpringContextTests {

   @Autowired
   private JobLauncherTestUtils jobLauncherTestUtils;

   @Bean
   public DataSource dataSource() {
      DriverManagerDataSource dataSource = new DriverManagerDataSource();
      dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
      dataSource.setUrl("url");
      dataSource.setUsername("username");
      dataSource.setPassword("password");

      return dataSource;
   }



   @Test
   public void testJob() throws Exception {

      JobExecution jobExecution = jobLauncherTestUtils.launchJob();

      Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode());
   }


}

Code is from spring documentation.

I am new to this stuff. Thanks for your help.

Stacktrace:

java.lang.IllegalStateException: Failed to load ApplicationContext 
   at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) 
   at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83) 

... ...

WORKING CODE:

 @RunWith(SpringRunner.class)
    @SpringBatchTest
    @ContextConfiguration(classes = {BatchProcessingJobApplication.class, DataSourceConfiguration.class, JobconfigurationTest.class, BatchProperties.class}, initializers=ConfigFileApplicationContextInitializer.class)
    @ActiveProfiles("test")
    public class BatchProcessingJobApplicationTests {

       @Autowired
       private JobLauncherTestUtils jobLauncherTestUtils;

    @Test
public void testStep() throws Exception {
    // given
    JobParameters jobParameters = jobLauncherTestUtils.getUniqueJobParameters();

    // when
    JobExecution jobExecution = jobLauncherTestUtils.launchStep("jobName", jobParameters);

    // then
    Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
}

    }


    public class DataSourceConfiguration {
    @Bean
        public DataSource dataSource(){

            SQLServerConnectionPoolDataSource dataSource = new SQLServerConnectionPoolDataSource();
              dataSource.setURL(url);
              dataSource.setUser(username);
              dataSource.setPassword(password);
              return dataSource;
        }
    }

    @Configuration
    public class JobconfigurationTest {
          @Bean
          public JobLauncherTestUtils jobLauncherTestUtils() {
                return new JobLauncherTestUtils();
          }

    }

Hope this helps someone like me.

PAA
  • 1
  • 46
  • 174
  • 282
Mia
  • 169
  • 1
  • 2
  • 11
  • First of all you should add correct parameters to `DataSource` bean, URL, PASSWORD and USER. Error log should contain some error messages because unble to load app.context this is a consequence of a previous error. – borino Mar 05 '19 at 04:07
  • Can you please share the **full** stacktrace of the error you get? – Mahmoud Ben Hassine Mar 05 '19 at 09:05
  • @borino I set corrct values for database perameters in my code, example above is just a placeholder. – Mia Mar 05 '19 at 15:30
  • Thanks for adding the stacktrace. Indeed, it shows that the job bean cannot be autowired in the `JobLauncherTestUtils`. So my answer should fix your problem. – Mahmoud Ben Hassine Mar 05 '19 at 20:58

1 Answers1

3

My aim is to be able to test individual steps.

You can use JobLauncherTestUtils#launchStep(String stepName) to launch a particular step and not the whole job. If you want to unit test a particular step, I would recommend to import only the configuration needed to test the step (I'm referring to RestTemplateConfig, SftpConfig, etc unless the step really need those).

Please note that the job under test should be declared as a bean in the test context because it is autowired in the JobLauncherTestUtils. In your case, I assume it is defined in the BatchProcessingConfiguration class.

A typical step test would be something like:

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBatchTest
@ContextConfiguration(classes = {JobConfiguration.class}) // contains the job/step definition
public class JobTest {

   @Autowired
   private JobLauncherTestUtils jobLauncherTestUtils;

   @Test
   public void testStep() throws Exception {
      // given
      JobParameters jobParameters = jobLauncherTestUtils.getUniqueJobParameters();

      // when
      JobExecution jobExecution = jobLauncherTestUtils.launchStep("myStep", jobParameters);

      // then
      Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
   }

}

NB: SpringBatchTest has been added in v4.1.

EDIT for additional comments: When the application context contains multiple job beans, it is not possible to know which one to inject in the JobLauncherTestUtils. In this case, the JobLauncherTestUtils should be created manually and configured with the job under test. Here is an example:

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersIncrementer;
import org.springframework.batch.core.JobParametersValidator;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.job.DefaultJobParametersValidator;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

/*
 * This test class shows how to use the JobLauncherTestUtils when the application
 * context contains multiple jobs. Since the job is autowired in JobLauncherTestUtils (see setter),
 * it is not possible to autowire one job (ambiguous injection). Hence, it is required to either:
 *  - Not autowire the JobLauncherTestUtils (as shown in this class)
 *  - Or separate job definitions and use a test class for each job (better solution IMO, each job has its own test)
 */
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestMultipleJobsWithJobLauncherTestUtils.JobsConfiguration.class)
public class TestMultipleJobsWithJobLauncherTestUtils {

    // don't autowire the JobLauncherTestUtils in this case otherwise No qualifying bean of type 'org.springframework.batch.core.Job' available: expected single matching bean but found 2: job1,job2
    private JobLauncherTestUtils jobLauncherTestUtils = new JobLauncherTestUtils();

    @Autowired
    private Job job1;

    @Autowired
    private Job job2;

    @Autowired
    private JobLauncher jobLauncher;
    @Autowired
    private JobRepository jobRepository;

    @Before
    public void setUp() {
        jobLauncherTestUtils.setJobLauncher(jobLauncher);
        jobLauncherTestUtils.setJobRepository(jobRepository);
    }

    @Test
    public void testJob1() throws Exception {
        // given
        jobLauncherTestUtils.setJob(job1);

        // when
        JobExecution jobExecution = jobLauncherTestUtils.launchJob();

        // then
        Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
    }

    @Test
    public void testJob2() throws Exception {
        // given
        jobLauncherTestUtils.setJob(job2);

        // when
        JobExecution jobExecution = jobLauncherTestUtils.launchJob();

        // then
        Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
    }

    @Configuration
    @EnableBatchProcessing
    public static class JobsConfiguration {

        @Bean
        public Job job1() {
            return new SimpleJob("job1");
        }

        @Bean
        public Job job2() {
            return new SimpleJob("job2");
        }

        // Don't declare the JobLauncherTestUtils as a bean to avoid dependecy injection
//      @Bean
//      public JobLauncherTestUtils jobLauncherTestUtils() {
//          return new JobLauncherTestUtils();
//      }

    }

    static class SimpleJob implements Job {

        private String name;

        public SimpleJob(String name) {
            this.name = name;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public boolean isRestartable() {
            return false;
        }

        @Override
        public void execute(JobExecution execution) {
            System.out.println("Executing job " + this.name);
            execution.setExitStatus(ExitStatus.COMPLETED);
        }

        @Nullable
        @Override
        public JobParametersIncrementer getJobParametersIncrementer() {
            return null;
        }

        @Override
        public JobParametersValidator getJobParametersValidator() {
            return new DefaultJobParametersValidator();
        }

    }
}

Hope this helps.

Mahmoud Ben Hassine
  • 28,519
  • 3
  • 32
  • 50
  • Thank you for your reply. I tried your sample code. Updated stacktrace in question – Mia Mar 05 '19 at 19:47
  • I meant to say that I updated the question with stacktrace information. – Mia Mar 05 '19 at 20:02
  • Thanks. I updated the answer accordingly. It should fix your issue. – Mahmoud Ben Hassine Mar 05 '19 at 21:02
  • I had to add initializers=ConfigFileApplicationContextInitializer.class to @ContextConfiguration annotation, but got this working finally! Updating the question with working code – Mia Mar 08 '19 at 18:37
  • Can I ask one more question, If I have 2 jobs in my job configuration, how to create JobLauncherTestUtils for both jobs? I am getting No unique bean exception. Tried steps from https://stackoverflow.com/questions/34217101/spring-batch-junit-test-for-multiple-jobs but does not help. – Mia Mar 08 '19 at 18:56
  • In this case, you need to create the `JobLauncherTestUtils` manually with `new` operator and not declare it as bean (So that Spring does not try to inject one of the job beans and fail to do it). Then you set the job you want to test on the `JobLauncherTestUtils` yourself. I re-edited the answer with a new example for this use case. – Mahmoud Ben Hassine Mar 08 '19 at 22:43
  • Thank you so much for the response! can't wait to implement this. I will update as soon as I get this working. – Mia Mar 09 '19 at 02:30
  • I could test multiple jobs successfully! Thanks for the sample. – Mia Mar 11 '19 at 20:14