Search code examples
javaspring-bootjunityaml

How to test ConfigurationProperties with JUnit?


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 enter image description here

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


Solution

  • 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

    SOLUTION 1

    @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 {
    
    }
    

    SOLUTION 2

    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);
        }
    
    }