This is my first time working with Externalized Configuration and yaml.
I created a yaml where I use the class name as KEY, and the field name as VALUE
YAML:
project:
test:
service:
computator:
# exclude field from beeing randomly valorized
population:
exclude:
InputClass: 'myDate'
AnotherClass: 'myName'
ExcludePopulationProperties:
@Data
@Component
@ConfigurationProperties(prefix = "project")
public class ExcludePopulationProperties {
private Test test;
@Data
public static class Test {
private Service service;
}
@Data
public static class Service {
private Computator computator;
}
@Data
public static class Computator {
private Population population;
}
@Data
public static class Population {
private Map<String, String> exclude;
}
}
Test with JUnit 5:
@ContextConfiguration(classes = { ExcludePopulationProperties.class })
@ExtendWith(SpringExtension.class)
class YamlTest {
@Autowired
private ExcludePopulationProperties excludePopulationProperties;
@Test
void testExternalConfiguration() {
Map<String, String> map = excludePopulationProperties.getTest().getService().getComputator().getPopulation().getExclude();
assertNotNull(map);
}
The problem is that I have a NullPointerException because test is null
So I'm not sure what is wrong here, I was expecting that the map was correctly populated.
I also tried to add
@TestPropertySource(properties = { "spring.config.location=classpath:application-_test.yaml" })
on the YamlTest
With these little changes, now I'm able to test the properties from YAML file.
I improved the yaml a little bit:
# test placeholders
project:
test:
service:
computator:
# exclude field from beeing randomly valorized
population:
exclude:
InputClass:
- 'myDate'
AnotherClass:
- 'myName'
so now the ExcludePopulationProperties have a Map<String, List<String>> instead of Map<String, String>, in this way I will be able to exclude more than one field from the same class
@Data
@Configuration
@ConfigurationProperties(prefix = "project")
public class ExcludePopulationProperties {
private Test test;
@Data
public static class Test {
private Service service;
}
@Data
public static class Service {
private Computator computator;
}
@Data
public static class Computator {
private Population population;
}
@Data
public static class Population {
private Map<String, List<String>> exclude;
}
}
YamlPropertySourceFactory is a class implemented by Baeldung in this guide: @PropertySource with YAML Files in Spring Boot
public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(resource.getResource().getFilename(), properties);
}
}
Test Class :
@ExtendWith(SpringExtension.class)
@EnableConfigurationProperties(value = { ExcludePopulationProperties.class })
@PropertySource(value = "classpath:application-_test.yaml", factory = YamlPropertySourceFactory.class)
class YamlTest {
@Autowired
private ExcludePopulationProperties excludePopulationProperties;
@Test
void testExternalConfiguration() {
Map<String, List<String>> map = excludePopulationProperties.getTest().getService().getComputator().getPopulation().getExclude();
assertNotNull(map);
}
}
Please note that for Mockito you need to use both, SpringExtension and MockitoExtension:
@Extensions({
@ExtendWith(SpringExtension.class),
@ExtendWith(MockitoExtension.class)
})
@EnableConfigurationProperties(value = { ExcludePopulationProperties.class })
@PropertySource(value = "classpath:application-_test.yaml", factory = YamlPropertySourceFactory.class)
class YamlTest {
}
In order to avoid writing the annotations on all test classes, add the jackson jackson-dataformat-yaml dependency
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson-dataformat-yaml.version}</version>
</dependency>
The configuration properties class will be:
@Data
public class ExcludePopulationProperties {
private Project project;
@Data
public static class Project {
private Test test;
}
@Data
public static class Test {
private Service service;
}
@Data
public static class Service {
private Computator computator;
}
@Data
public static class Computator {
private Population population;
}
@Data
public static class Population {
private Map<String, List<String>> exclude;
}
}
somewhere write a method to read ExcludePopulationProperties using jackson YAMLMapper:
public static ExcludePopulationProperties buildExcludePopulationProperties() throws IOException {
InputStream inputStream = new FileInputStream(new File("./src/test/resources/" + "application-_test.yaml"));
YAMLMapper mapper = new YAMLMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
return mapper.readValue(inputStream, ExcludePopulationProperties.class);
}
then, wherever you need, simply call the static method, and the test class will be simplier (from my point of view) :
@ExtendWith(SpringExtension.class)
class YamlTest {
@Test
void testExternalConfiguration() throws IOException {
Map<String, List<String>> map = MyTestUtils.buildExcludePopulationProperties().getProject().getTest().getService().getComputator().getPopulation().getExclude();
assertNotNull(map);
}
}