Spring @PropertySource using YAML
SpringSpring BootSpring Problem Overview
Spring Boot allows us to replace our application.properties
files with YAML equivalents. However, I seem to hit a snag with my tests. If I annotate my TestConfiguration
(a simple Java config), it is expecting a properties file.
For example this doesn't work:
@PropertySource(value = "classpath:application-test.yml")
If I have this in my YAML file:
db:
url: jdbc:oracle:thin:@pathToMyDb
username: someUser
password: fakePassword
And I'd be leveraging those values with something like this:
@Value("${db.username}") String username
However, I end up with an error like so:
Could not resolve placeholder 'db.username' in string value "${db.username}"
How can I leverage the YAML goodness in my tests as well?
Spring Solutions
Solution 1 - Spring
Spring-boot has a helper for this, just add
@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
at the top of your test classes or an abstract test superclass.
Edit: I wrote this answer five years ago. It doesn't work with recent versions of Spring Boot. This is what I do now (please translate the Kotlin to Java if necessary):
@TestPropertySource(locations=["classpath:application.yml"])
@ContextConfiguration(
initializers=[ConfigFileApplicationContextInitializer::class]
)
is added to the top, then
@Configuration
open class TestConfig {
@Bean
open fun propertiesResolver(): PropertySourcesPlaceholderConfigurer {
return PropertySourcesPlaceholderConfigurer()
}
}
to the context.
Solution 2 - Spring
As it was mentioned @PropertySource
doesn't load yaml file. As a workaround load the file on your own and add loaded properties to Environment
.
Implemement ApplicationContextInitializer
:
public class YamlFileApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Resource resource = applicationContext.getResource("classpath:file.yml");
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Add your initializer to your test:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
@Test
public test(){
// test your properties
}
}
Solution 3 - Spring
@PropertySource
can be configured by factory
argument. So you can do something like:
@PropertySource(value = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class)
Where YamlPropertyLoaderFactory
is your custom property loader:
public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
if (resource == null){
return super.createPropertySource(name, resource);
}
return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null);
}
}
Inspired by https://stackoverflow.com/a/45882447/4527110
Solution 4 - Spring
Another option is to set the spring.config.location
through @TestPropertySource
:
@TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }
Solution 5 - Spring
@PropertySource
only supports properties files (it's a limitation from Spring, not Boot itself). Feel free to open a feature request ticket in JIRA.
Solution 6 - Spring
From Spring Boot 1.4, you can use the new @SpringBootTest
annotation to achieve this more easily (and to simplify your integration test setup in general) by bootstrapping your integration tests using Spring Boot support.
Details on the Spring Blog.
As far as I can tell, this means you get all the benefits of Spring Boot's externalized config goodness just like in your production code, including automatically picking up YAML config from the classpath.
By default, this annotation will
> ... first attempt to load @Configuration
from any inner-classes, and if that fails, it will search for your primary @SpringBootApplication
class.
but you can specify other configuration classes if required.
For this particular case, you can combine @SpringBootTest
with @ActiveProfiles( "test" )
and Spring will pick up your YAML config, provided it follows the normal Boot naming standards (i.e. application-test.yml
).
@RunWith( SpringRunner.class )
@SpringBootTest
@ActiveProfiles( "test" )
public class SpringBootITest {
@Value("${db.username}")
private String username;
@Autowired
private MyBean myBean;
...
}
Note: SpringRunner.class
is the new name for SpringJUnit4ClassRunner.class
Solution 7 - Spring
The approach to loading the yaml properties, IMHO can be done in two ways:
a. You can put the configuration in a standard location - application.yml
in the classpath root - typically src/main/resources
and this yaml property should automatically get loaded by Spring boot with the flattened path name that you have mentioned.
b. The second approach is a little more extensive, basically define a class to hold your properties this way:
@ConfigurationProperties(path="classpath:/appprops.yml", name="db")
public class DbProperties {
private String url;
private String username;
private String password;
...
}
So essentially this is saying that load the yaml file and populate the DbProperties class based on the root element of "db".
Now to use it in any class you will have to do this:
@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {
@Autowired private DbProperties dbProperties;
}
Either of these approaches should work for you cleanly using Spring-boot.
Solution 8 - Spring
Since Spring Boot 2.4.0 you can use ConfigDataApplicationContextInitializer as follows:
@SpringJUnitConfig(
classes = { UserAccountPropertiesTest.TestConfig.class },
initializers = { ConfigDataApplicationContextInitializer.class }
)
class UserAccountPropertiesTest {
@Configuration
@EnableConfigurationProperties(UserAccountProperties.class)
static class TestConfig { }
@Autowired
UserAccountProperties userAccountProperties;
@Test
void getAccessTokenExpireIn() {
assertThat(userAccountProperties.getAccessTokenExpireIn()).isEqualTo(120);
}
@Test
void getRefreshTokenExpireIn() {
assertThat(userAccountProperties.getRefreshTokenExpireIn()).isEqualTo(604800);
}
}
See also: https://www.baeldung.com/spring-boot-testing-configurationproperties#YAML-binding
Solution 9 - Spring
I found a workaround by using @ActiveProfiles("test")
and adding an application-test.yml file to src/test/resources.
It ended up looking like this:
@SpringApplicationConfiguration(classes = Application.class, initializers = ConfigFileApplicationContextInitializer.class)
@ActiveProfiles("test")
public abstract class AbstractIntegrationTest extends AbstractTransactionalJUnit4SpringContextTests {
}
The file application-test.yml just contains the properties that I want to override from application.yml (which can be found in src/main/resources).
Solution 10 - Spring
it's because you have not configure snakeyml. spring boot come with @EnableAutoConfiguration feature. there is snakeyml config too when u call this annotation..
this is my way:
@Configuration
@EnableAutoConfiguration
public class AppContextTest {
}
here is my test:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(
classes = {
AppContextTest.class,
JaxbConfiguration.class,
}
)
public class JaxbTest {
//tests are ommited
}
Solution 11 - Spring
I needed to read some properties into my code and this works with spring-boot 1.3.0.RELEASE
@Autowired
private ConfigurableListableBeanFactory beanFactory;
// access a properties.yml file like properties
@Bean
public PropertySource properties() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("properties.yml"));
propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
// properties need to be processed by beanfactory to be accessible after
propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
return propertySourcesPlaceholderConfigurer.getAppliedPropertySources().get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME);
}
Solution 12 - Spring
Loading custom yml file with multiple profile config in Spring Boot.
-
Add the property bean with SpringBootApplication start up as follows
@SpringBootApplication @ComponentScan({"com.example.as.*"}) public class TestApplication {
public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Bean @Profile("dev") public PropertySourcesPlaceholderConfigurer propertiesStage() { return properties("dev"); } @Bean @Profile("stage") public PropertySourcesPlaceholderConfigurer propertiesDev() { return properties("stage"); } @Bean @Profile("default") public PropertySourcesPlaceholderConfigurer propertiesDefault() { return properties("default"); } /** * Update custom specific yml file with profile configuration. * @param profile * @return */ public static PropertySourcesPlaceholderConfigurer properties(String profile) { PropertySourcesPlaceholderConfigurer propertyConfig = null; YamlPropertiesFactoryBean yaml = null; propertyConfig = new PropertySourcesPlaceholderConfigurer(); yaml = new YamlPropertiesFactoryBean(); yaml.setDocumentMatchers(new SpringProfileDocumentMatcher(profile));// load profile filter. yaml.setResources(new ClassPathResource("env_config/test-service-config.yml")); propertyConfig.setProperties(yaml.getObject()); return propertyConfig; }
}
-
Config the Java pojo object as follows
@Component @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(Include.NON_NULL) @ConfigurationProperties(prefix = "test-service") public class TestConfig {
@JsonProperty("id") private String id; @JsonProperty("name") private String name; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; }
}
-
Create the custom yml (and place it under resource path as follows, YML File name : test-service-config.yml
Eg Config in the yml file.
test-service:
id: default_id
name: Default application config
---
spring:
profiles: dev
test-service:
id: dev_id
name: dev application config
---
spring:
profiles: stage
test-service:
id: stage_id
name: stage application config
Solution 13 - Spring
Enhancing Mateusz Balbus answer.
Modified YamlFileApplicationContextInitializer
class where YAML location is defined per test class. It does not work per test, unfortunately.
public abstract class YamlFileApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
/***
* Return location of a YAML file, e.g.: classpath:file.yml
*
* @return YAML file location
*/
protected abstract String getResourceLocation();
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Resource resource = applicationContext.getResource(getResourceLocation());
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Usage:
Create subclass of YamlFileApplicationContextInitializer
with defined getResourceLocation()
method and add this subclass into @SpringApplicationConfiguration
annotation.
This way it is easiest to make the test class itself.
@RunWith(SpringRunner.class)
@SpringApplicationConfiguration(classes = Application.class, initializers = SimpleTest.class)
public class SimpleTest extends YamlFileApplicationContextInitializer {
@Override
protected String getResourceLocation() {
return "classpath:test_specific.yml";
}
@Test
public test(){
// test your properties
}
}
Solution 14 - Spring
<dependency>
<groupId>com.github.yingzhuo</groupId>
<artifactId>spring-boot-stater-env</artifactId>
<version>0.0.3</version>
</dependency>
Welcome to use my library. Now yaml, toml, hocon is supported.
Source: github.com
Solution 15 - Spring
This is not an answer to the original question, but an alternative solution for a need to have a different configuration in a test...
Instead of @PropertySource
you can use -Dspring.config.additional-location=classpath:application-tests.yml
.
Be aware, that suffix tests
does not mean profile...
In that one YAML file one can specify multiple profiles, that can kind of inherit from each other, read more here - https://stackoverflow.com/questions/60333056/property-resolving-for-multiple-spring-profiles-yaml-configuration
Then, you can specify in your test, that active profiles (using @ActiveProfiles("profile1,profile2")
) are profile1,profile2
where profile2
will simply override (some, one does not need to override all) properties from profile1
.
Solution 16 - Spring
I have tried all of the listed questions, but all of them not work for my task: using specific yaml file for some unit test. In my case, it works like this:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(initializers = {ConfigFileApplicationContextInitializer.class})
@TestPropertySource(properties = {"spring.config.location=file:../path/to/specific/config/application.yml"})
public class SomeTest {
@Value("${my.property.value:#{null}}")
private String value;
@Test
public void test() {
System.out.println("value = " + value);
}
}
Solution 17 - Spring
project demo url: https://github.com/Forest10/spring-boot-family/tree/spring-boot-with-yml
I run this answer in my prod env!!! so if you against this ans. please test first!!!
There is no need to add like YamlPropertyLoaderFactory or YamlFileApplicationContextInitializer. You should convert your idea
Follow these steps:
Just add applicationContext.xml like
@ImportResource({"classpath:applicationContext.xml"})
to your ApplicationMainClass.
and your applicationContext.xml should write like this
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-autowire="byName"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:property-placeholder location="classpath*:*.yml"/>
</beans>
This can help scan your application-test.yml
db:
url: jdbc:oracle:thin:@pathToMyDb
username: someUser
password: fakePassword