How to test Classes with @ConfigurationProperties and @Autowired

Solution 1:

You need to annotate your TestConfiguraion with @EnableConfigurationProperties as follows:

@EnableConfigurationProperties
public class TestConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "test")
    public TestSettings settings (){
        return new TestSettings();
    }
}

Also you only need to include TestConfiguration.class in @ContextConfiguration of you SettingsTest class:

@TestPropertySource(locations = "/SettingsTest.properties")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
public class SettingsTest {
...

Solution 2:

A couple points:

  1. You don't need a "TestConfiguration" class in your main package, because all it's doing is configuring the "TestSettings" bean. You can do this simply by annotating the TestSettings class itself.

  2. Normally you would load the context you need for the test using the @SpringApplicationConfiguration annotation, passing the name of your Application class. However, you said you don't want to load the whole ApplicationContext (though it's not clear why), so you need to create a special configuration class to do the loading only for tests. Below I call it "TestConfigurationNew" to avoid confusion with the TestConfiguration class that you had originally.

  3. In the Spring Boot world, all properties are generally kept in the "application.properties" file; but it is possible to store them elsewhere. Below, I have specified the "SettingsTest.properties" file that you proposed. Note that you can have two copies of this file, the one in the main/resources folder, and the one in the test/resources folder for testing.

Change the code as follows:

TestSettings.java (in main package)

@Configuration
@ConfigurationProperties(prefix="test", locations = "classpath:SettingsTest.properties")
public class TestSettings {

    private String property;

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }
}

SettingsTest.java (in test package)

@TestPropertySource(locations="classpath:SettingsTest.properties")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfigurationNew.class)
public class SettingsTest {

    @Autowired
    TestSettings settings;

    @Test
    public void testConfig(){
        Assert.assertEquals("TEST_PROPERTY", settings.getProperty());
    }
}

TestConfigurationNew.java (in test package):

@EnableAutoConfiguration
@ComponentScan(basePackages = { "my.package.main" })
@Configuration
public class TestConfigurationNew {
}

This should now work the way you wanted.

Solution 3:

you can actually just add @EnableConfigurationProperties to your @SpringBootTest directly.
eg:

@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestConfiguration.class)
@EnableConfigurationProperties
...

Solution 4:

If you use Spring Boot, now you only need:

@RunWith(SpringRunner.class)
@SpringBootTest

No extra @ContextConfiguration, no extra class only for tests to EnableAutoConfiguration and EnableConfigurationProperties. You don't have to specify the configuration class to load, they will all be loaded.

But, ensure the properties entries you want to read in main/resources/application.yml is also present in test/resources/application.yml. Repetition is unavoidable.


Another way is:

  1. Define a class of configuration only for tests, along with MyApplicationTest.java, at the same level. This class can be empty.

Like:

@EnableAutoConfiguration
@EnableConfigurationProperties(value = {
        ConnectionPoolConfig.class
})
public class MyApplicationTestConfiguration {
}
  1. And, in the class to load the autowired configuration.

Like:

@RunWith(SpringRunner.class)
//@SpringBootTest // the first, easy way
@ContextConfiguration(classes = MyApplicationTestConfiguration.class,
        initializers = ConfigFileApplicationContextInitializer.class)
public class ConnectionPoolConfigTest {

    @Autowired
    private ConnectionPoolConfig config;

Basically, you:

  • use a specific configuration to @EnableConfigurationProperties and @EnableAutoConfiguration, listing all the @ConfigurationProperties files you want to load
  • in the test class, you load this configuration file of tests, with an initializer class defined by Spring to load application.yml file.

And, put the values to load in test/resources/application.yml. Repetition is unavoidable. If you need load another file, use @TestProperties() with a location. Note: @TestProperties only supports .properties files.


Both way works for configuration class loading values

  • either from application.yml/application.properties
  • or from another properties file, specified by PropertySource, like @PropertySource(value = "classpath:threadpool.properties")

Important

Last notes from Spring doc, as per here

Some people use Project Lombok to add getters and setters automatically. Make sure that Lombok does not generate any particular constructor for such a type, as it is used automatically by the container to instantiate the object.

Finally, only standard Java Bean properties are considered and binding on static properties is not supported.

That means, if you have lombok.@Builder without @NoArgsConstructor nor @AllArgsConstructor, properties injection will not happen because it only sees the invisible constructor created by @Builder. So, be sure to use none, or all of these annotations!

Solution 5:

Unit test

To avoid having to load a Spring context, we can use the Binder class, which is also used internally by Spring anyway.

// A map of my properties.
Map<String, String> properties = new HashMap<>();
properties.put("my-prefix.first-property", "foo");
properties.put("my-prefix.second-property", "bar");

// Creates a source backed by my map, you can chose another type of source as needed.
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties)

// Binds my properties to a class that maps them.
Binder binder = new Binder(source);
BindResult<MyConfiguration> result = binder.bind("my-prefix", MyConfiguration.class);

// Should return true if bound successfully.
Assertions.assertTrue(result.isBound);

// Asserts configuration values.
MyConfiguration config = result.get();
Assertions.assertEquals("foo", config.getFirstProperty());
Assertions.assertEquals("bar", config.getSecondProperty());