SpringBootTest Property Placeholders Not Resolved - java

I have a properties file with two values defined as follows:
company.service.host=https://myserver.mycompany.com/services
company.services.clients=${company.service.host}/clients
When used within my Spring Boot application, the property substitution works as expected when used within a class called ClientServiceUriBuilder. However, when I set up a test class like this (imports omitted for brevity):
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ClientServiceUriBuilder.class)
#PropertySource("classpath:application.properties")
public class TestTest {
#Autowired()
ClientServiceUriBuilder uut;
#Test()
public void shouldGetClientUrl() {
String url = uut.getClientGetUrl(12345);
System.out.println(url);
}
}
The resulting url should be:
https://myserver.mycompany.com/services/clients/12345
But what I'm getting is:
${service.host}/clients/12345
The portion of the placeholder prior to the first "." is stripped, and the rest of the unresolved placeholder is returned.
Am I doing something wrong, or is this a bug in the Spring Boot testing suite?

Related

#TestPropertySource Class Could not resolve placeholder

I'm creating a TestClass using a class Propiedades which has #PropertySource annotation.
#Component
#PropertySource("file:${services.properties}test-app/.properties")
public class Propiedades {
#Value("${db.name}")
public String dbName;
#Value("${db.jndi}")
public String dbJNDI;
#Value("${db.owner}")
public String dbOwner;
}
As you can see I'm loading the .properties file from a external location relative to the server where the app is deployed. services.properties is a env variable for the server which has a route to a specific folder.
Here is my test class
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = Propiedades.class)
#TestPropertySource(locations = "/application-test.properties")
public class IncidenciaServicioServiceImplTest {
#Autowired
Propiedades propiedades;
#Test
public void isPropertySetup() {
String output = propiedades.dbName;
Assert.assertEquals("testDb", output);
}
}
When running the test I get the following error.
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [Propiedades]; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'services.properties' in value "file:${services.properties}test-app/.properties"
Why is it trying to load the properties from that path?, shouldn't it be reading from src/test/resources/application-test.properties?
As far as I know Spring does not work like you expect. It always tries to load properties specified in #PropertySource first then overrides them with values provided with #TestPropertySource annotation.
So one of the ways to workaround your issue is to use ignoreResourceNotFound flag for #PropertySource annotation:
#PropertySource("file:${services.properties}test-app/.properties", ignoreResourceNotFound = true)
public class Propiedades {
// ... your properties
}
In this case Spring will ignore the fact it can not find the properties specified with #PropertySource and continue using values from #TestPropertySource
Or alternatively, you need to provide value for your ${services.properties} env variable while tests run so it is resolved to correct path to property file.

Recommended way to remove #DataJpaTest when used along with #SpringBootTest

We have an application that relies on Spring Boot 2.0. We are in the process of migrating it to JDK11 from JDK8. This also enabled us to update Spring Boot from 2.0 to 2.1. After reading through the changelog, it appeared there was any major change that needed for us.
Now the problem lies in where some test classes are annotated with both #SpringBootTest and #DataJpaTest. As per this and as well as the documentation, we are not supposed to use both together and instead we changed #DataJpaTest to #AutoConfigureTestDatabase. Here is how the code is:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {A.class, B.class}, properties = {
"x=xxx",
"y=yyy"
})
#AutoConfigureTestDatabase // Used to be #DataJpaTest
#EnableJpaRepositories("com.test")
#EntityScan("com.test")
public class Test {
#TestConfiguration
public static class TestConfig {
// Some beans returning
}
// Tests
}
Now, we end up with the following error:
NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available
So as per this answer, we did something like this:
#EnableJpaRepositories(basePackages="com.test", entityManagerFactoryRef="entityManagerFactory")
Even after this we still end up with the same error. Is this the right way to remove #DataJpaTest? Or do we need to remove #SpringBootTest and do something else? Any sort of guidance is much appreciated.
The testclass is annotated with #DataJpaTest and #ContextConfiguration
#RunWith(SpringRunner.class)
#DataJpaTest
#ContextConfiguration(locations = { "classpath:test-context.xml" })
public abstract class AbstractTestCase {
protected static final Logger LOG = LoggerFactory.getLogger(AbstractTestCase.class);
}
We defined a test-context.xml. This is because the testmodule is isolated from all other modules (multi maven module project). In the test-context.xml we defined the component-scan for the base-package.
<context:component-scan base-package="de.example.base.package" />

Unit testing of Spring Boot Actuator endpoints not working when specifying a port

recently I changed my spring boot properties to define a management port.
In doing so, my unit tests started to fail :(
I wrote a unit test that tested the /metrics endpoint as follows:
#RunWith (SpringRunner.class)
#DirtiesContext
#SpringBootTest
public class MetricsTest {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
/**
* Called before each test.
*/
#Before
public void setUp() {
this.context.getBean(MetricsEndpoint.class).setEnabled(true);
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
/**
* Test for home page.
*
* #throws Exception On failure.
*/
#Test
public void home()
throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get("/metrics"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
Previously this was passing. After adding:
management.port=9001
The tests started failing with:
home Failed: java.lang.AssertionError: Status expected: <200> but was: <404>
I tried changing the #SpringBootTest annotation with:
#SpringBootTest (properties = {"management.port=<server.port>"})
Where is the number used for the server.port. This didn't seem to make any difference.
So then changed the management.port value in the property file to be the same as the server.port. Same result.
The only way to get the test to work is remove the management.port from the property file.
Any suggestions/thoughts ?
Thanks
For Spring Boot 2.x the integration tests configuration could be simplified.
For example simple custom heartbeat endpoint
#Component
#Endpoint(id = "heartbeat")
public class HeartbeatEndpoint {
#ReadOperation
public String heartbeat() {
return "";
}
}
Where integration test for this endpoint
#SpringBootTest(
classes = HeartbeatEndpointTest.Config.class,
properties = {
"management.endpoint.heartbeat.enabled=true",
"management.endpoints.web.exposure.include=heartbeat"
})
#AutoConfigureMockMvc
#EnableAutoConfiguration
class HeartbeatEndpointTest {
private static final String ENDPOINT_PATH = "/actuator/heartbeat";
#Autowired
private MockMvc mockMvc;
#Test
void testHeartbeat() throws Exception {
mockMvc
.perform(get(ENDPOINT_PATH))
.andExpect(status().isOk())
.andExpect(content().string(""));
}
#Configuration
#Import(ProcessorTestConfig.class)
static class Config {
#Bean
public HeartbeatEndpoint heartbeatEndpoint() {
return new HeartbeatEndpoint();
}
}
}
For Spring boot test we need to specify the port it needs to connect to.
By default, it connects to server.port which in case of actuators is different.
This can be done by
#SpringBootTest(properties = "server.port=8090")
in application.properties we specify the management port as below
...
management.server.port=8090
...
Did you try adding the following annotation to your test class?
#TestPropertySource(properties = {"management.port=0"})
Check the following link for reference.
Isn't there an error in the property name?
Shouldn't be
#TestPropertySource(properties = {"management.server.port=..."}) instead of #TestPropertySource(properties = {"management.port=.."})
The guide stated that this can be achieved with #AutoConfigureMetrics.
And I moved with this.
Regardless of your classpath, meter registries, except the in-memory backed, are not auto-configured when using #SpringBootTest.
If you need to export metrics to a different backend as part of an integration test, annotate it with #AutoConfigureMetrics.
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.metrics
Had the same issue, you just have to make the management.port null by adding this in your application-test.properties (set it to empty value)
management.port=
Make sure you use the test profile in your JUnit by annotating the class with
#ActiveProfiles("test")
Try using
#SpringBootTest(properties = {"management.port="})
Properties defined in the #SpringBootTest annotation have a higher precedence than those in application properties. "management.port=" will "unset" the management.port property.
This way you don't have to worry about configuring the port in your tests.
I was facing the same issue and tried several things but this is how I was able to solve mine without making any change in the application.yaml
Sample actuator endpoint
#Component
#RestControllerEndpoint(id = "endpoint")
public class SampleEndpoint
{
#GetMapping
public String sampleEndpoint(){
return ""
}
}
Unit test case
#RunWith(SpringRunner.class)
#SpringBootTest(
classes = {SampleEndpointTest.Config.class},
properties = {"management.server.port="}
)
#AutoConfigureMockMvc
public class SampleEndpointTest
{
#Autowired
private MockMvc mockMvc;
#SpringBootApplication(scanBasePackageClasses = {SampleEndpoint.class})
public static class Config
{
}
#Test
public void testSampleEndpoint() throws Exception
{
mockMvc.perform(
MockMvcRequestBuilders.get("/actuator/enpoint").accept(APPLICATION_JSON)
).andExpect(status().isOk());
}
Since now info endpoint must be enabled manually make sure the SpringBootTest tag includes this in properties, like this:
#SpringBootTest(
properties = {
"management.info.env.enabled=true" ,
"management.endpoints.web.exposure.include=info, health"
})
I had this problem recently, and as none of the above answers made any sense to me, I decided to do a bit more reading. In my case, I had already defined both server.port and management.server.port as 8091 in my test application-test.yaml file, and could not understand why my test was getting a connection refused error message.
It turns out that instead of using the annotation #SpringBootTest() I needed to use #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) - which causes the port numbers in the yaml file to be used. This is briefly discussed in the manual. Quoting the relevant section:
DEFINED_PORT — Loads an EmbeddedWebApplicationContext and provides a real servlet environment. Embedded servlet containers are started and listening on a defined port (i.e from your application.properties or on the default port 8080).
It seems in SpringBootTest the default is to avoid starting a real servlet environment, and if no WebEnvironment is explicitly specified then SpringBootTest.WebEnvironment.MOCK is used as a default.
After a long search: There is this nice Springboot annotation called #LocalManagementPort!
It works similar to #LocalServerPort but for actuator endpoins.
An example config would look as follows
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MetricsIT {
#Autowired
RestTemplateBuilder restTemplateBuilder;
#LocalManagementPort
int managementPort;
#Test
public void testMetrics(){
ResponseEntity<String> response = restTemplateBuilder
.rootUri("http://localhost:" + managementPort + "/actuator")
.build().exchange("/metrics", HttpMethod.GET, new HttpEntity<>(null), String.class);
}
}

#Value "Could not resolve placeholder" in Spring Boot Test

I want to take a Junit test for Spring-boot as below:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ApplicationTest.class})
public class TestOnSpring {
#Value("${app.name}")
private String appName;
#Test
public void testValue(){
System.out.println(appName);
}
}
and ApplicationTest.java like this
#ComponentScan("org.nerve.jiepu")
#EnableAutoConfiguration()
public class ApplicationTest {
public static void main(String[] args) {
SpringApplication.run(ApplicationTest.class, args);
}
}
and my POM like this:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version>
</parent>
When I run the test, I got below error information
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'app.name' in string value "${app.name}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174)
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:204)
at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:178)
at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$2.resolveStringValue(PropertySourcesPlaceholderConfigurer.java:172)
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:807)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1027)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:543)
... 31 more
But When I run this application as normal Java Application
#SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
It work well!
What's wrong with it ? How should I take the junit test with Spring-boot?
Thanks a lot!
You need to add
#PropertySource("classpath:application.properties")
to your class, so it will pick your normal configurations.
If you need different configurations for test you can add
#TestPropertySource(locations="classpath:test.properties")
If not just copy paste your config file to test/resources folder, then boot will pick from there.
See this.
You can use the #SpringBootTest that will do create the PropertySourcesPlaceholderConfigurer automatically.
This is described in the Testing chapter of the Spring Boot documentation.
http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-configfileapplicationcontextinitializer-test-utility
You have annotated your test class with #ContextConfiguration(classes = {ApplicationTest.class}). Wherein ApplicationTest.class does the component scan on a mentioned package. When you run your test it tries to find the configuration from the resources folder in 'main' instead of 'test'. If you annotate your class with #SpringBootTest(classes = {ClassToBeTested.class}) or just #SpringBootTest in this particular case, I think (not 100% sure) it will create a limited context and pick up the properties from test/resources.
If your properties are test specific, you can name your properties/yml file as application-test.properties or application-test.yml. And use #ActiveProfiles("test") in your test class so that it will always read test specific properties file.
I usually use this solution which works for me.
For me the issue was related to the profile set for the application properties.
application.yml:
spring:
config:
activate:
on-profile: local
mongodb:
uri: mongodb+srv:/
Class with reference to value:
#Value("${mongodb.uri}")
private String uri;
Resolution in test class method:
#SpringBootTest
#ActiveProfiles("local")
class myTestClass {

How to test Classes with #ConfigurationProperties and #Autowired

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.
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 {
...
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.
you can actually just add #EnableConfigurationProperties to your #SpringBootTest directly.
eg:
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestConfiguration.class)
#EnableConfigurationProperties
...
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 {
}
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!
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());

Categories