Using Property Configuration Holders with Plain Spring TestContext Framework

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

Internal

Overview

Property configuration holders are a Spring Boot pattern. They require Spring Boot dependencies and runtime to function. However, they're useful, so this is a method to get them to work with plain TestContext Framework tests.

Procedure

To configure tests to load configuration properties from ./src/test/resources/application.yml:

1. Add the following dependencies:

implementation('org.springframework.boot:spring-boot')
testImplementation('org.yaml:snakeyaml:1.23')

2. Configure the test with a custom initializer.

@RunWith(SpringRunner.class)
@ContextConfiguration(
  classes = {...},
  initializers = PropertyAwareTestContextInitializer.class)
public class ExampleTests {
  ...
}

3. This is the custom initializer.

...
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class PropertyAwareTestContextInitializer 
  implements ApplicationContextInitializer<ConfigurableApplicationContext> {

  @Override
  public void initialize(ConfigurableApplicationContext applicationContext) {

    //
    // expose ./src/test/resources/application.yml as a property source
    //

    new ConfigFileApplicationListener().postProcessEnvironment(
      applicationContext.getEnvironment(), new SpringApplication());

    //
    // register a BeanPostProcessor that loads the property values from the environment into
    // @ConfigurationProperties classes
    //

    applicationContext.addBeanFactoryPostProcessor(
      beanFactory -> beanFactory.addBeanPostProcessor(
        applicationContext.getBean(PropertyAwareTestContextConfiguration.class).
          getConfigurationPropertiesBindingPostProcessor()));
  }
}

4. Provide additional required beans in the form of a @Configuration:

...

import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class PropertyAwareTestContextConfiguration {

  @Bean
  public ConfigurationPropertiesBindingPostProcessor getConfigurationPropertiesBindingPostProcessor() {
    return new ConfigurationPropertiesBindingPostProcessor();
  }

  @Bean("org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata")
  public ConfigurationBeanFactoryMetadata getConfigurationBeanFactoryMetadata() {
    return new ConfigurationBeanFactoryMetadata();
  }
}

5. List the @Configuration among those the test is configured with:

@RunWith(SpringRunner.class)
@ContextConfiguration(
  classes = {
    ...,
    PropertyAwareTestContextConfiguration.class},
  initializers = PropertyAwareContextInitializer.class)
public class ExampleTests {
  ...
}

Playground Example

https://github.com/ovidiuf/playground/tree/master/spring/testing/02-integration-test-with-config-property-holder