How to set different classpath in an unit test to load resource with Spring
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 {
// ...
}
Aure77
Updated on June 19, 2022Comments
-
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
usingclasspath: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 useapplication-test1.properties
andapplication-test2.properties
. On my test class, I can set@ActiveProfiles("test1")
to activate a profile and load associated properties.