How to test Classes with @ConfigurationProperties and @Autowired
JavaPropertiesSpring BootJava Problem Overview
I want to test small parts of the application that rely on properties loaded with @Autowired
and @ConfigurationProperties
. I am looking for a solution loading only the required properties and not always the whole ApplicationContext
.
Here as reduced example:
@TestPropertySource(locations = "/SettingsTest.properties")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestSettings.class, TestConfiguration.class})
public class SettingsTest {
@Autowired
TestConfiguration config;
@Test
public void testConfig(){
Assert.assertEquals("TEST_PROPERTY", config.settings().getProperty());
}
}
Configuration Class:
public class TestConfiguration {
@Bean
@ConfigurationProperties(prefix = "test")
public TestSettings settings (){
return new TestSettings();
}
}
Settings Class:
public class TestSettings {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}
The properties file in the resource folder contains the entry:
test.property=TEST_PROPERTY
In my current setup config is not null, but no fields are available. The reason the fields are not field should have something to do with the fact that I am not using Springboot but Spring. So what would be the Springboot way to get this running?
edit: The reason why I want to do this is: I have a parser that parses Textfiles, the regular expressions used are stored in a properties file. To test this I would like to load only the properties needed for this parser which are in the exaple above the TestSettings.
While reading the comments I already noticed that this are no Unit tests anymore. However using the full Spring boot configuration for this small test seems a bit too much to me. That's why I asked if there is a posibilty to load only the one class with properties.
Java Solutions
Solution 1 - Java
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 - Java
A couple points:
-
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.
-
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.
-
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 - Java
you can actually just add @EnableConfigurationProperties to your @SpringBootTest directly.
eg:
@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestConfiguration.class)
@EnableConfigurationProperties
...
Solution 4 - Java
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:
- 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 {
}
2. 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 - Java
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());