How to test @ConfigurationProperties with ApplicationContextRunner from spring-boot-test?
Asked Answered
M

2

9

I need to test my autoconfiguration classes that make use of @ConfigurationProperties beans. I'm making use of ApplicationContextRunner as documented in https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-test-autoconfig to make tests faster and avoid starting the servlet container between each variations. However, beans annotated with @AutoconfigurationProperties are not populated with values injected into ApplicationContextRunner.

I suspect that I'm hitting problem similar to https://mcmap.net/q/1314497/-spring-boot-configurationproperties-not-satisfied-on-test

@ConfigurationProperties are not managed by the application context you build in tests, although they will be load when the application launches, because you have @EnableConfigurationProperties on your app main class.

How can I enable support for @ConfigurationProperties with ApplicationContextRunner ?

Here is the corresponding code

    @Test
    void ServiceDefinitionMapperPropertiesAreProperlyLoaded() {
        ApplicationContextRunner contextRunner = new ApplicationContextRunner()
            .withConfiguration(AutoConfigurations.of(
                SingleServiceDefinitionAnswerAutoConfig.class,
                DynamicCatalogServiceAutoConfiguration.class
            ))
//          .withPropertyValues(DynamicCatalogProperties.OPT_IN_PROPERTY + "=true") //Not sure why this seems ignored
            .withSystemProperties(DynamicCatalogConstants.OPT_IN_PROPERTY + "=true",
                ServiceDefinitionMapperProperties.PROPERTY_PREFIX
                +ServiceDefinitionMapperProperties.SUFFIX_PROPERTY_KEY+ "=suffix")
        ;
        contextRunner.run(context -> {
            assertThat(context).hasSingleBean(ServiceDefinitionMapperProperties.class);
            ServiceDefinitionMapperProperties serviceDefinitionMapperProperties
                = context.getBean(ServiceDefinitionMapperProperties.class);
            assertThat(serviceDefinitionMapperProperties.getSuffix()).isEqualTo("suffix");
        });
    }

which fails with:

 org.opentest4j.AssertionFailedError: 
Expecting:
 <"">
to be equal to:
 <"suffix">
but was not.
Expected :suffix
Actual   :
<Click to see difference>
    at org.springframework.cloud.appbroker.autoconfigure.DynamicCatalogServiceAutoConfigurationTest
public class DynamicCatalogServiceAutoConfiguration {

[...]

    @Bean
    @ConfigurationProperties(prefix=ServiceDefinitionMapperProperties.PROPERTY_PREFIX, ignoreUnknownFields = false)
    public ServiceDefinitionMapperProperties serviceDefinitionMapperProperties() {
        return new ServiceDefinitionMapperProperties();
    }
[...]
}

Full sources available at https://github.com/orange-cloudfoundry/osb-cmdb-spike/blob/0da641e5f2f811f48b0676a25c8cbe97895168d1/spring-cloud-app-broker-autoconfigure/src/test/java/org/springframework/cloud/appbroker/autoconfigure/DynamicCatalogServiceAutoConfigurationTest.java#L89-L107

ps: I was about to submit an issue to https://github.com/spring-projects/spring-boot/issues to suggest documentation enhancement to warn of such limitation in ApplicationContext, and to ask for ways to turn on support for @ConfigurationProperties. Following guidance at https://raw.githubusercontent.com/spring-projects/spring-boot/master/.github/ISSUE_TEMPLATE.md, I'm first making sure here I'm not misunderstanding the problem.

Mothball answered 5/12, 2019 at 8:24 Comment(0)
H
6

As far as I can tell, none of the classes involved in your test enable configuration property binding. As a result, no properties are bound to ServiceDefinitionMapperProperties. You can enable configuration property binding using @EnableConfigurationProperties. A typical place to add it would be on DynamicCatalogServiceAutoConfiguration as its serviceDefinitionMapperProperties bean relies on configuration properties being enabled.

Honea answered 5/12, 2019 at 10:48 Comment(1)
Thanks! Adding @EnableConfigurationProperties fixed the test. I had misread the previously quoted answer into thinking the context implementation class was not supporting configuration properties. Sorry about the noise.Mothball
B
7

If you want to populate a bean annotated with @ConfigurationProperties class as part of your test, and you normally depend on a configuration class annotated with @EnableConfigurationProperties to populate that bean, then you can create a trivial configuration class just for the test:

@ConfigurationProperties("app")
public class ConfigProps {
    private int meaningOfLife;

    public int getMeaningOfLife() { return meaningOfLife; }
    public void setMeaningOfLife(int meaning) { this.meaningOfLife = meaning; }
}

class ConfigPropsTest {

    private final ApplicationContextRunner runner = new ApplicationContextRunner();

    @EnableConfigurationProperties(ConfigProps.class)
    static class TrivialConfiguration {
    }

    @Test
    void test() {
        runner.withUserConfiguration(TrivialConfiguration.class)
            .withPropertyValues("app.meaning-of-life=42")
            .run(context -> {
                assertEquals(42, context.getBean(ConfigProps.class).getMeaningOfLife());
            });
    }

}

Passing TrivialConfiguration to the ApplicationContextRunner is sufficient to make it create ConfigProps and populate it using the available properties.

Brooks answered 10/7, 2020 at 21:13 Comment(2)
This is a great answser - thanks. Do you think it's bad practice for the class itself (eg ConfigProps) to have the @EnableConfigurationProperties set on it directly? I guess that's bad because if writing a library, would want the actual application to determine if enabling config properites is ok or not.Myatt
To anyone reading this answer, don't forget to make your properties public. I had same exact problem but didn't have public getter/setter access on my props and could not find the issue!Myatt
H
6

As far as I can tell, none of the classes involved in your test enable configuration property binding. As a result, no properties are bound to ServiceDefinitionMapperProperties. You can enable configuration property binding using @EnableConfigurationProperties. A typical place to add it would be on DynamicCatalogServiceAutoConfiguration as its serviceDefinitionMapperProperties bean relies on configuration properties being enabled.

Honea answered 5/12, 2019 at 10:48 Comment(1)
Thanks! Adding @EnableConfigurationProperties fixed the test. I had misread the previously quoted answer into thinking the context implementation class was not supporting configuration properties. Sorry about the noise.Mothball

© 2022 - 2024 — McMap. All rights reserved.