2013/03/25
Using & Mocking PropertySource & Environment in Spring 3.2
PropertySource and Environment was added to Spring 3.1, these classes simplify working with properties. In Spring 3.2 MockEnvironment and MockPropertySource were also added making it much easier to mock properties in tests. As the existing approach was not removed (just slightly updated) there are now several ways to use and configure the use of properties in Spring. I wrote this article to explain the options not least because at the moment this topic doesn't seem to be covered fully on the internet.
For a working example in code of these techniques see the github project https://github.com/jamesdbloom/base_spring_mvc_web_application
Before Spring 3.1 the only way to register a properties file was by:
<context:property-placeholder location="some.properties"/>
In Spring 3.1 the @PropertySource annotation was introduced in JavaConfig as follows:
@Configuration @PropertySource("classpath:some.properties") public class ApplicationConfiguration
In addition, from Spring 3.1, the underlying object created by using <context:property-placeholder> is a new class called PropertySourcesPlaceholderConfigurer. This new object is more flexible and now interacts with Environment and PropertySource both also introduced in Spring 3.1.
Note: to understand how this XML tag drives the addition of a PropertySourcesPlaceholderConfigurer to the ApplicationContext see ContextNamespaceHandler and PropertyPlaceholderBeanDefinitionParser in org.springframework.context.config.
In Spring 3.2 two new classes where introduced to support mocking properties these are MockEnvironment and MockPropertySource. Environment and PropertySources are used during bean creation so any mock versions needs to be added into the ApplicationContext before bean creation, this was not previously easy. To solve this the @ContextConfiguration was extended to add support for an ApplicationContextInitializer to provide access to the Environment and PropertySource in the ApplicationContext prior to bean creation, as shown below.
For a working example in code of the techniques below see the github project https://github.com/jamesdbloom/base_spring_mvc_web_application
Using PropertySource in JavaConfig
In @Configuration the recommended way to work with properties is to use Environment and @PropertySource as follows:
@Configuration @PropertySource("classpath:some.properties") public class ApplicationConfiguration
Then inject the environment:
@Inject private Environment environment;
Then when creating beans use the Environment to read properties:
environment.getProperty("foo.bar")
For example construct an MVC Interceptor that takes a property, as follows:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SomeInterceptorThatTakeAProperty(environment.getProperty("some.property"))); }
Or create a bean as follow:
@Bean public SomeBean someBean() { return new SomeBean(environment.getProperty("some.property")); }
Note: If you use this approach @Value will no longer work. See below for how you can still use @Value.
Mocking PropertySource
To mock a PropertySource add the following class / inner class to your test
public static class PropertyMockingApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources(); MockPropertySource mockEnvVars = new MockPropertySource().withProperty("bundling.enabled", false); propertySources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars); } }
Then add this class to the list of initializers in the @ContextConfiguration
@ContextConfiguration(classes = ApplicationConfiguration.class, initializers = MyIntegrationTest.TestApplicationContextInitializer.class)
Or you can add a MockEnvironment as follows:
public static class PropertyMockingApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { MockEnvironment mockEnvironment = new MockEnvironment(); applicationContext.setEnvironment(mockEnvironment); } }
For a working example in code of these techniques see the github project https://github.com/jamesdbloom/base_spring_mvc_web_application
Using @Value in Spring 3.2
Using the @PropertySource annotation on your @Configuration does not add a PropertySourcesPlaceholderConfigurer (or a PropertyPlaceholderConfigurer). These two mechanisms are independent and the recommended new approach is to use environment.getProperty("some.value") instead of "${some.value}". So if you want to continue using @Value annotation you need to add an @Bean that creates the PropertySourcesPlaceholderConfigurer
@Bean public static PropertySourcesPlaceholderConfigurer properties() { PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer(); propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource("some.properties")); return propertySourcesPlaceholderConfigurer; }
Then to mock this add an inner @Configuration class to your test (or test superclass)
@Configuration public static class PropertyMockingConfiguration { @Bean public static PropertySourcesPlaceholderConfigurer properties() { PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer(); propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource("test.properties")); return propertySourcesPlaceholderConfigurer; } }
And add that class to the list of classes on the @ContextConfiguration. Note: if you have no classes or locations listed on the @ContextConfiguration then the default location for configuration will be any inner static class of the test with the @Configuration annotation.
@ContextConfiguration(classes = {ApplicationConfiguration.class, MyIntegrationTest.PropertyMockingConfiguration.class})