Passing TestPropertySource location has no affect - java

I am trying to pass a test.properties file to the #TestPropertySource annotation like so:
#SpringBootTest
#TestPropertySource(locations = "classpath:test.properties")
public class ApplicationTests {
#Test
public void contextLoads() {
}
#Test
public void main() {
Application.main(new String[] {});
}
}
I then call the main application, hoping that test.properties will be taken. The Application.main is a standard Boot application:
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Perhaps I have misunderstood the intended purpose of #TestPropertySource?
Thanks for any help

The following is written in the documentation:
#TestPropertySource is a class-level annotation that is used to
configure the locations() of properties files and inlined properties()
to be added to the Environment's set of PropertySources for an
ApplicationContext for integration tests.
So basically, test property sources can be used to selectively override properties
defined in system and application property source.
If #TestPropertySource is declared as an empty annotation (i.e., without explicit values for locations() or properties()), an attempt will be made to detect a default properties file relative to the class that declared the annotation. For example, if the annotated test class is com.example.MyTest, the corresponding default properties file is "classpath:com/example/MyTest.properties". If the default cannot be detected, an IllegalStateException will be thrown.
In your case, file test.properties should be placed in resources folder.
You can check this example to see how it works in action.

Related

Testing a Spring Boot command line application

I'd like to test my Spring Boot command line application. I would like to mock certain beans (which I was able to do by annotating #ContextConfiguration(classes = TestConfig.class) at the top of my test class. In TestConfig.class, I override the beans that I would like to mock. I'd like Spring Boot to find the rest of the components. This seems to work.
The problem is that when I run the test, the entire application starts up as normal (ie. the run() method is called).
#Component
public class MyRunner implements CommandLineRunner {
//fields
#Autowired
public MyRunner(Bean1 bean1, Bean2 bean2) {
// constructor code
}
#Override
public void run(String... args) throws Exception {
// run method implementation
}
I've tried to override the MyRunner #Bean and put it in TestConfig.class, but that doesn't seem to work. I understand that I'm loading the regular application context, but that's what I'd like to do (I think?) since I would like to re-use all (or most) of the #Component I created in my Application, and only mock a tiny subset.
Any suggestions?
EDIT:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The answer was simpler than I thought. Add the MockBean in
#TestConfiguration
public class TestConfig {
#MockBean
private MyRunner myRunner;
}
We can use the #MockBean to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context.
So MyRunner.run() is never called but I can still use all the other beans in my application.
CommandLineRunners are ordinary beans with one exception:
After the application context is loaded, spring boot finds among all its beans the beans that implement this interface and calls their run method automatically.
Now, I would like you to ask to do the following:
Remove ContextConfiguration from the test and place a breakpoint in constructor of MyRunner. The test should look like this:
#RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
#Autowired
private MyRunner myRunner;
#Test
public void testMe() {
System.out.println("hello");
}
}
Run the test and make sure that myRunner is loaded and its run method is called
Now mock this class with MockBean annotation:
#RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
#MockBean
private MyRunner myRunner;
#Test
public void testMe() {
System.out.println("hello");
}
}
Run the test. Make sure that run method is not running. Your Application Context now should contain a mock implementation of your component.
If the above works, then the problem is with TestConfig and ContextConfiguration annotation. In general when you run without ContextConfiguration you give spring boot test engine a freedom to mimic the application context started as if its a real application (with autoconfigurations, property resolution, recursive bean scanning and so forth).
However if you put ContextConfiguration, spring boot test doesn't work like this - instead it only loads the beans that you've specified in that configuration. No Autoconfigurations, no recursive bean scanning happens for example.
Update
Based on OP's comment:
It looks like the MyRunner gets loaded when you put #ContextConfiguration because of component scanning. Since you have an annotation #Component placed on MyRunner class it can be discovered by Spring boot engine.
In fact there is a "dangerous" mix of two types of beans definitions here:
1. The beans defined with #Bean in #Configuration annotation
2. The beans found during component scanning.
Here is the question for you: If you don't want to mimic the application startup process and instead prefer to load only specific beans, why do you use #SpringBootTest at all? Maybe you can achieve the goal with:
#RunWith(SpringRunner.class)
#ContextConfiguration(YourConfig.class)
public class MyTest {
...
}
One way you could do this is to have 2 classes with the main method, one which sets up the "normal" context, and another that sets up the "mock" context:
Normal App Context, uses the usual Application
#SpringBootApplication(scanBasePackages = "com.example.demo.api")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public Foo foo() {
return new Foo("I am not mocked");
}
#Bean
public Bar bar() {
return new Bar("this is never mocked");
}
}
Add another Application class that overrides the normal context with the mocked one
#SpringBootApplication(scanBasePackageClasses = {MockApplication.class, Application.class})
#Component
public class MockApplication {
public static void main(String[] args) {
SpringApplication.run(MockApplication.class, args);
}
#Bean
public Foo foo() {
return new Foo("I am mocked");
}
}
When you run Application.main Foo will be "I am not mocked", when you run MockApplication.main() it will be "I am mocked"

Spring data multiple datasources from multiple files

I have 2 (or more) different configuration properties file located in the project and I want to load them for different datasources.
I tried to do as following:
#Bean
#ConfigurationProperties(locations = {"#{myconfigroot.getRootFolder()}/datasource1.properties"}
public static DataSource getFirstDatasource() {
return DataSourceBuilder.create().build();
}
But obviously this won't work as the ConfigurationProperties annotation locations property doesn't go through the spEL. (Or may be I write it wrong?) myconfigroot.getRootFolder() is a static method which returns the path to the datasource1.properties.
Please advice. Thanks.
===== Edited =======
I believe this is a common problem when somebody want their application want to load different configuration files. Due to some reasons the file location and name can't be put in the startup script or command line, or, the path can only be determined in runtime, that would require spring to load them during the bean creation.
I tried once using PropertySourcePlaceHolderConfigurer but seems not work either.
Anybody can share some lights?
Latest Spring boot (version 1.3.5) doesn’t support SpEL in this case.
See JavaDoc of annotation #ConfigurationProperties
Note that contrary to {#code #Value}, SpEL expressions are not
evaluated since property values are externalized.
I found a way to customize Spring boot default behavior as follows:
For example, I have database.properties file in somewhere, for some reason I cannot get the location before runtime.
username=mike
password=password
Accordingly, define POJO mapping to properties:
#Component
#ConfigurationProperties(locations = "myConfiguration")// myConfiguration is customized placeholder
public class MyProperties{
String username;
String password;
//Getters, Setters…
}
Then, to extend default StandardEnvironment:
public class MyEnvironment extends StandardEnvironment {
#Override
public String resolvePlaceholders(String location) {
if (location.equals("myConfiguration")) {
//Whatever you can do, SpEL, method call...
//Return database.properties path at runtime in this case
return getRootFolder() + "datasource.properties";
} else {
return super.resolvePlaceholders(text);
}
}
}
Last, apply it in Spring boot main method entry:
#SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
new SpeedRestApplication()
.configure(new SpringApplicationBuilder(SpeedRestApplication.class).environment(new MyEnvironment()))//Replace default StandardEnvironment
.run(args);
}
}
Once Spring boot starts up, the MyProperties bean name and password fields are injected from database.properties. Then you could wire the MyProperties bean to other beans as configuration.
Hope it helps!
I finally got it work by using the following mechanism:
public class DatasourcePostProcessor implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Properties p = new Properties();
p.load(new FileInputStream(new File(getRootFolder() + "/datasource1.properties")));
Map<String, Object> propMap = new HashMap<>();
for (Map.Entry<Object, Object> entry : p.entrySet()) {
propMap.put(entry.getKey().toString(), entry.getValue());
}
MapPropertySource source = new MapPropertySource("datasource1", propMap);
environment.getPropertySources().addLast(source);
}
}
and register the environment post processor into the spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=com.myorg.test.DatasourcePostProcessor
Anyway, hope this helps people and accept the first anwer as it enlight me. Also post the following references from the google search that found during research:
Where I found how to wire the property source with the environment: https://github.com/spring-projects/spring-boot/issues/4595
Where I found how to load the customized properties file: How to configure a custom source to feed Spring Boot's #ConfigurationProperties

#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 {

Test Spring for case when context should not even load

I would like to test my Spring Boot application against case when no config file is given. In such situation application should throw exception when creating bean MyConfig.
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Bean
public MyConfig myConfig() throws IOException {
if (no config file) throw new NoConfigFileException();
}
}
I have a test which tests if Spring application's context is built:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#TestPropertySource(locations="classpath:file_with_existing_config_file_path.properties")
public class MyApplicationTest {
#Test
public void contextLoads() {
}
}
This test fails - as expected (since myConfig method throws NoConfigFileException). Unfortunately I cannot "turn the light to green" using annotation #Test(expected = NoConfigFileException.class).
Where should I expect exception if not in the only one test method I got?
Golden rule when you are writing automated tests is -> cover one test case per test method (unless you are having parametrized test). It seems to me that you are thinking about breaking that rule.
Consider separate test class (where you don't specify properties file) which is testing only this aspect or your logic. That way you can use #Test(expected = NoConfigFileException.class).
BTW, I would suggest to look into Spring Boot feature #ConfigurationProperties. You can use Java EE validation (e.g. #NotNull) for your properties.
That way you can enforce application to find the file and fail it early if no config file is loaded into Spring Context.

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