How to set different classpath in an unit test to load resource with Spring

15,481

First of all you need to understand how JUnit tests with Spring work. The purpose of SpringJUnit4ClassRunner is to create the ApplicationContext for you (using @ContextConfiguration). You do not need to create the context yourself.

If the context is properly set up, you may then use @Autowired to get the dependencies you need in your test. ExtractBatchTestCase should look something like this:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { ExtractionBatchConfiguration.class })
public class ExtractBatchTestCase {

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private JobRepository jobRepository;

    @Autowired
    @Qualifier("extractJob1")
    private Job job;

    private JobLauncherTestUtils jobLauncherTestUtils;

    @Before
    public void setup() throws Exception {      
        jobLauncherTestUtils = new JobLauncherTestUtils();
        jobLauncherTestUtils.setJobLauncher(jobLauncher);
        jobLauncherTestUtils.setJobRepository(jobRepository);
    }

    @Test
    public void testGeneratedFiles() throws Exception {     
        jobLauncherTestUtils.setJob(job);
        JobExecution jobExecution = jobLauncherTestUtils.launchJob();
        Assert.assertNotNull(jobExecution);
        Assert.assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
        Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
        // ... other assert
    }

}

Second, the Javadoc for @ProperySource states:

In cases where a given property key exists in more than one .properties file, the last @PropertySource annotation processed will 'win' and override. [...]

In certain situations, it may not be possible or practical to tightly control property source ordering when using @ProperySource annotations. For example, if the @Configuration classes [...] were registered via component-scanning, the ordering is difficult to predict. In such cases - and if overriding is important - it is recommended that the user fall back to using the programmatic PropertySource API.

Create an ApplicationContextInitializer for your tests to add some test properties with highest search priority that will always 'win':

public class MockApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        MockPropertySource mockEnvVars = new MockPropertySource().withProperty("foo", "bar");
        propertySources.addFirst(mockEnvVars);
    }
}

Declare it using @ContextConfiguration:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { ExtractionBatchConfiguration.class }, 
                      initializers = MockApplicationContextInitializer.class)
public class ExtractBatchTestCase {
    // ...
}
Share:
15,481
Aure77
Author by

Aure77

Updated on June 19, 2022

Comments

  • Aure77
    Aure77 almost 2 years

    I would like to create 2 test case with JUnit and Spring that requires both the same classpath resource batch-configuration.properties but content of this file differ depending on test.

    Actually in my maven project, I create these file tree :

    • src/test/resources/test1/batch-configuration.properties
    • src/test/resources/test2/batch-configuration.properties

    But how can I define my root classpath depending on my test case (files are loaded in ExtractionBatchConfiguration using classpath:batch-configuration.properties)

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = { ExtractionBatchConfiguration.class }, loader = AnnotationConfigContextLoader.class)
    @PropertySource("classpath:test1/batch-configuration.properties") // does not override ExtractionBatchConfiguration declaration
    public class ExtractBatchTestCase {
        private static ConfigurableApplicationContext context;
    
        private JobLauncherTestUtils jobLauncherTestUtils;
    
        @BeforeClass
        public static void beforeClass() {
            context = SpringApplication.run(ExtractionBatchConfiguration.class);
        }
    
        @Before
        public void setup() throws Exception {      
            jobLauncherTestUtils = new JobLauncherTestUtils();
            jobLauncherTestUtils.setJobLauncher(context.getBean(JobLauncher.class));
            jobLauncherTestUtils.setJobRepository(context.getBean(JobRepository.class));
        }
    
        @Test
        public void testGeneratedFiles() throws Exception {     
            jobLauncherTestUtils.setJob(context.getBean("extractJob1", Job.class));
            JobExecution jobExecution = jobLauncherTestUtils.launchJob();
            Assert.assertNotNull(jobExecution);
            Assert.assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
            Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
            // ... other assert
        }
    
    }
    

    Config :

    @Configuration
    @EnableAutoConfiguration
    @PropertySources({
        @PropertySource("batch-default-configuration.properties"),
        @PropertySource("batch-configuration.properties")
    })
    public class ExtractionBatchConfiguration { /* ... */ }
    

    I am using Spring 4.0.9 (I cannot use 4.1.x) and JUnit 4.11

    EDIT:

    After using custom ApplicationContextInitializer suggested by hzpz to override my properties locations (application.properties + batch-configuration.properties) that solve some problems, I am experiencing another problem with @ConfigurationProperties :

    @ConfigurationProperties(prefix = "spring.ldap.contextsource"/*, locations = "application.properties"*/) 
    public class LdapSourceProperties { 
        String url;
        String userDn;
        String password;
        /* getters, setters */
    }
    

    and configuration :

    @Configuration
    @EnableConfigurationProperties(LdapSourceProperties.class)
    public class LdapConfiguration {
        @Bean
        public ContextSource contextSource(LdapSourceProperties properties) {
            LdapContextSource contextSource = new LdapContextSource();
            contextSource.setUrl(properties.getUrl());
            contextSource.setUserDn(properties.getUserDn());
            contextSource.setPassword(properties.getPassword());
            return contextSource;
        }
    }
    

    All LdapSourceProperties's field are null when ContextSource is created but if I uncomment locations = "application.properties" it's only works if application.properties is in the root classpath. The default environment used by @ConfigurationProperties seems doesn't contains neested properties...

    Alternative solution :

    Finally I put all my properties into application-<profile>.properties files (and remove @PropertySource definition). I can now use application-test1.properties and application-test2.properties. On my test class, I can set @ActiveProfiles("test1") to activate a profile and load associated properties.