Spring Boot - How to read properties from multiple custom yml - java

We are upgrading the spring boot version from 1.3.0.RELEASE to 2.3.12.RELEASE.
As per the old version, yml files were read using the following code snippet
#Configuration
#ConfigurationProperties(locations = "classpath:/config/myconf-source.yml")
public class MyConfigProperties {
private String configSource;
public String getConfigSource() {
return configSource;
}
public void setConfigSource(String configSource) {
this.configSource = configSource;
}
}
Config files in src/main/resources/config/
myconf-source.yml
news-source.yml
conf-mapping.yml
Content in myconf-source.yml
configSource: "TEST"
Corresponding Test Class
#ActiveProfiles("test")
#RunWith(SpringJUnit4ClassRunner.class)
#EnableAutoConfiguration
#SpringApplicationConfiguration(classes = SampleApplication.class)
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
#ConfigurationProperties(locations = "classpath:**/config/**")
public class MyConfigPropertiesTest {
#Autowired
private MyConfigProperties myConfigProperties;
#Test
public void testMyConfigProperties() {
String config = myConfigProperties.getConfigSource();
Assert.assertEquals(config, "TEST");
}
}
After changing to the new version, it throws an error Cannot resolve method 'locations'.
If I remove locations attribute how spring will know the class MyConfigProperties has to read myconf-source.yml
Also while running the test class, NullPointerException is thrown as myConfigProperties.getConfigSource(); becomes null.
I have tried various solutions posted but no luck,
Can anyone suggest how to make it work?
Thanks

#Configuration should be used if in that class you define beans with #Bean.
If not then remove it from there.
Also #Configuration does not make this class a bean to be autowired in the test that you require it to be.
If you want MyConfigProperties to be available for autowiring then you also need
#EnableConfigurationProperties(MyConfigProperties.class). This will make sure that this class is available as a spring bean in the application context.
So it would be
#PropertySource("classpath:/config/myconf-source.yml")
#ConfigurationProperties()
#EnableConfigurationProperties(MyConfigProperties.class)
public class MyConfigProperties {
private String configSource;
public String getConfigSource() {
return configSource;
}
public void setConfigSource(String configSource) {
this.configSource = configSource;
}
}

You can use #PropertySource annotation to read the yml file , you can read the below article :
https://www.baeldung.com/properties-with-spring

Related

Java Spring Boot #Autowired values are null

When I try to print the variable that I have autowired, it prints "null" instead of the value I set it to, "Example." I can't quite figure out what I'm missing
In my AppConfig class I have:
#Configuration
public class ApplicationConfiguration {
#Bean
public String tableName(){
return "Example";
}
}
In my other class, DAOMethods, that I want to autowire the variable in:
#Component
public class DAOMethods {
#Autowired
private String tableName;
public void print(){
System.out.println(tableName);
}
}
They exist in different packages; With AppConfig living in a config
folder and DAOMethods in client->dynamodb->util folder. Config and
Client are folders under the main->java folder
The added #Configuration annotation scans for the beans in the current and its subpackages. You need to explicitly tell the application to scan the required packages. So you can do:
#SpringBootApplication (scanBasePackages = {"config", "client"})
OR,
You need to keep the config and other classes which uses that config in same root package. You can put the config folder and client folder under same package, say com.application and then add the following in your main class:
#SpringBootApplication(scanBasePackages = "com.application")
Now run the application.
You problem have many solutions
F.e. you can create config-class with required set of parameters and next autowire it (Injecting values with #Value annotation from application configuration file is good practice):
#Component
public class CustomConfiguration {
#Value("${table.name}")
private String tableName;
#Value("${some.value}")
private Integer someValue;
public String getTableName() {
return tableName;
}
public Integer getsomeValue() {
return someValue;
}
}
And you application.properties will looks like:
some.value=1
table.name=Example
Or you can simply inject single value from configuration with #Value annotation
One of solutions is using bean name in #Value annotation:
#Configuration
public class ApplicationConfiguration {
#Bean
public String tableName(){
return "Example";
}
}
#Component
public class DAOMethods {
#Value(#{tableName})
private String tableName;
}
More examples you can see in this question: Autowire a string from Spring #Configuration class?

Test spring configuration with ConditionalOnCloudPlatform annotation

I am trying to test my Spring configuration class which is annotated with ConditionalOnCloudPlatform.
Here is a very simplified example of the configuration class (I can't post my actual code):
#Configuration
#ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
public class CloudConfigurationExample {
#Bean
public MyBean myBean(MyProperties properties) {
return new MyBean(properties.getParam);
}
}
To test I was hoping to do this:
#RunWith(MockitoJUnitRunner.class)
public class CloudConfigurationExampleTest {
private CloudConfigurationExample cloudConfigurationExample;
private MyProperties myProperties;
#Before
public void setUp() {
myProperties = new MyProperies();
myProperties.setParam("test");
cloudConfigurationExample = new CloudConfigurationExample(myProperties);
}
#Test
public void test() {
MyBean myBean = cloudConfigurationExample.myBean();
// do asserts etc.
}
}
The issue I have is that ConditionalOnCloudPlatform is activated and expects a valid cloud connector to be present. As a result I get No suitable cloud connector found.
Does anyone know the correct way so get Junit to ignore this annotation? I tried setting an environment variable with VCAP_SERVICES, which is what this annotation expects, but it didn't work.
Thanks!
ConditionalOnCloudPlatform gets activated if environment contains properties VCAP_APPLICATION and VCAP_SERVICES
There are different ways to overcome this issue,
Firstly, ensure no properties containing above prefixes are passed.
Secondly, check for cloud profile #Profile("cloud") or ignore this class during test #Profile("!test") and many more ways.
code snippet:
CLOUD_FOUNDRY {
#Override
public boolean isActive(Environment environment) {
return environment.containsProperty("VCAP_APPLICATION")
|| environment.containsProperty("VCAP_SERVICES");
}
},

Spring Boot test with yaml properties by profile

So there's a lot of hits on this topic, but none of them have worked for me.
I have a very simple configuration class:
#Configuration
#ConfigurationProperties(prefix = "props")
public class TagIncluder {
private static final String PARAMETER_NAME = "tags";
private List<String> tags;
public TagIncluder() {
tags = new ArrayList<>();
}
public List<String> getTags() {
return tags;
}
#Handler
public void attachIncludedTags(Exchange exchange) {
exchange.getIn().setHeader(PARAMETER_NAME, tags);
}
}
I want this class to be able to load different property files. I am using yaml, and my file is named application-tag_test.yml. I have tried placing this file in src/main/resources, src/test/resources and src/test/resources/config, but it is never picked up.
This is the contents of the property file:
props:
tags:
- test
And finally, the test case:
#SpringBootTest
#ActiveProfiles("tag_test")
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TagIncluder.class)
public class TagIncluderTest extends ExchangeTestSupport {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
Exchange testExchange = createExchange();
sut.attachIncludedTags(testExchange);
Assertions.assertThat(testExchange.getIn().getHeader("tags", List.class))
.size().isGreaterThanOrEqualTo(1);
}
}
Additionally, I have tried placing an application.properties file in the above described locations with the following content:
spring.profiles.active=tag_test
What is required for Spring to set my yaml file as the desired configuration for my test class under test?
UPDATE
So after some exploration and trial and error, I have found that the following works:
#SpringBootTest
#ActiveProfiles("tag_test")
#RunWith(SpringJUnit4ClassRunner.class)
public class TagIncluderTest extends ExchangeTestSupport {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
Exchange testExchange = createExchange();
sut.attachIncludedTags(testExchange);
Assertions.assertThat(testExchange.getIn().getHeader("tags", List.class))
.size().isGreaterThanOrEqualTo(1);
}
}
The difference here is that I've removed the #ContextConfiguration annotation and I let Spring take care of all of that.
It is a lot slower, and I would prefer specifying what is needed. I think this might break in the future, for instance if I add another configuration class that will start with the entire context and throw errors because those properties are not included in my application-tag_test.yml configuration.
Finally, any of the above locations I tried for the configuration is valid with the above annotations. The application.properties to specify a profile is not needed.
If anyone knows a way to specify what should be loaded into the context instead, I'd be very grateful for another solution.
With some guidance of Jans suggestion above, I've managed to isolate the test to a slice. Auto configured testing is written about here, however that only touches on Springs predefined #..Test annotations.
If you dive deeper into the #WebMvcTest, for instance, you will find the #ImportAutoConfiguration annotation.
Using this, we can tell our test class to enable auto configuration for a single slice of our application. A tutorial is available here. The full list of factories available for auto configuration can be found in the spring-boot repository.
Finally, this is the entire test class:
#ActiveProfiles("tag_test")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = TagIncluder.class)
#ImportAutoConfiguration(classes = ConfigurationPropertiesAutoConfiguration.class)
public class TagIncluderTest extends ExchangeTestSupport {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
Exchange testExchange = createExchange();
sut.attachIncludedTags(testExchange);
Assertions.assertThat(testExchange.getIn().getHeader("tags", List.class))
.size().isGreaterThanOrEqualTo(1);
}
}
The class under test is untouched.
So now we can:
Use profiles
Use yaml
Test only our desired class in Spring Context
This has been very enlightening :)
The Spring Boot Test documentations states that
External properties, logging, and other features of Spring Boot are installed in the context by default only if you use SpringApplication to create it.
This means that you need to have a working Spring Boot Application in order to test anything related to property loading in a test case.
Also, setting a list from properties needs a setter. This works:
#Configuration
#ConfigurationProperties(prefix = "props")
public class TagIncluder {
private List<String> tags;
public void setTags(List<String> tags) {
this.tags = tags;
}
public List<String> getTags() {
return tags;
}
}
#Component
public class MyComponent {
#Autowired
TagIncluder tagIncluder;
}
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#SpringBootTest
#ActiveProfiles("tag_test")
#RunWith(SpringRunner.class)
public class TagIncluderTest {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
System.out.println(sut.getTags());
}
}

Changing Spring Boot Properties Programmatically

I'm trying to write tests for an application that uses #RefreshScope. I would like to add a test that actually changes out properties and asserts that the application responds correctly. I have figured out how to trigger the refresh (autowiring in RefreshScope and calling refresh(...)), but I haven't figured out a way to modify the properties. If possible, I'd like to write directly to the properties source (rather than having to work with files), but I'm not sure where to look.
Update
Here's an example of what I'm looking for:
public class SomeClassWithAProperty {
#Value{"my.property"}
private String myProperty;
public String getMyProperty() { ... }
}
public class SomeOtherBean {
public SomeOtherBean(SomeClassWithAProperty classWithProp) { ... }
public String getGreeting() {
return "Hello " + classWithProp.getMyProperty() + "!";
}
}
#Configuration
public class ConfigClass {
#Bean
#RefreshScope
SomeClassWithAProperty someClassWithAProperty() { ...}
#Bean
SomeOtherBean someOtherBean() {
return new SomeOtherBean(someClassWithAProperty());
}
}
public class MyAppIT {
private static final DEFAULT_MY_PROP_VALUE = "World";
#Autowired
public SomeOtherBean otherBean;
#Autowired
public RefreshScope refreshScope;
#Test
public void testRefresh() {
assertEquals("Hello World!", otherBean.getGreeting());
[DO SOMETHING HERE TO CHANGE my.property TO "Mars"]
refreshScope.refreshAll();
assertEquals("Hello Mars!", otherBean.getGreeting());
}
}
You could do this (I assume you mistakenly omitted the JUnit annotations at the top of your sample, so I'll add them back for you):
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class MyAppIT {
#Autowired
public ConfigurableEnvironment environment;
#Autowired
public SomeOtherBean otherBean;
#Autowired
public RefreshScope refreshScope;
#Test
public void testRefresh() {
assertEquals("Hello World!", otherBean.getGreeting());
EnvironmentTestUtils.addEnvironment(environment, "my.property=Mars");
refreshScope.refreshAll();
assertEquals("Hello Mars!", otherBean.getGreeting());
}
}
But you aren't really testing your code, only the refresh scope features of Spring Cloud (which are already tested extensively for this kind of behaviour).
I'm pretty sure you could have got this from the existing tests for refresh scope as well.
Properties used in the application must be variables annotated with #Value. These variables must belong to a class that is managed by Spring, like in a class with the #Component annotation.
If you want to change the value of the properties file, you can set up different profiles and have various .properties files for each profile.
We should note that these files are meant to be static and loaded once, so changing them programmatically is sort of out of the scope of ther intended use. However, you could set up a simple REST endpoint in a spring boot app that modifies the file on the host's file system (most likely in the jar file you are deploying) and then calls Refresh on the original spring boot app.

#ComponentScan not working in test with spring-boot-starter-test

I am attempting to test my #Service and #Repository classes in my project with spring-boot-starter-test and #Autowired is not working for the classes I'm testing.
Unit test:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes = HelloWorldConfiguration.class
//#SpringApplicationConfiguration(classes = HelloWorldRs.class)
//#ComponentScan(basePackages = {"com.me.sbworkshop", "com.me.sbworkshop.service"})
//#ConfigurationProperties("helloworld")
//#EnableAutoConfiguration
//#ActiveProfiles("test")
// THIS CLASS IS IN src/test/java/ AND BUILDS INTO target/test-classes
public class HelloWorldTest {
#Autowired
HelloWorldMessageService helloWorldMessageService;
public static final String EXPECTED = "je pense donc je suis-TESTING123";
#Test
public void testGetMessage() {
String result = helloWorldMessageService.getMessage();
Assert.assertEquals(EXPECTED, result);
}
}
Service:
#Service
#ConfigurationProperties("helloworld")
// THIS CLASS IS IN /src/main/java AND BUILDS INTO target/classes
public class HelloWorldMessageService {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message=message;
}
}
The commented class annotations on the unit test represent the various things I've tried to get this working. The test and the project packages are in the same package paths and the #ComponentScan works fine from my entry point (#RestController class with main method). The service #ComponentScan's and #Autowire's fine in my #RestController class in the src/main/java side, but does not in the test. I am required to add it again as a #Bean in my #Configuration class in order for #Autowired to work. The class is otherwise in scope just fine and I can reference and instantiate it just fine from the test. The problem appears to be that #ComponentScan does not appear to correctly traverse multiple entries in my test runner classpath, in this case /target/test-classes and /target/classes.
The IDE I am using is IntelliJ IDEA 13.
UPDATE - here are HelloWorldRs and its config:
#RestController
#EnableAutoConfiguration
#ComponentScan
public class HelloWorldRs {
// SPRING BOOT ENTRY POINT - main() method
public static void main(String[] args) {
SpringApplication.run(HelloWorldRs.class);
}
#Autowired
HelloWorldMessageService helloWorldMessageService;
#RequestMapping("/helloWorld")
public String helloWorld() {
return helloWorldMessageService.getMessage();
}
}
...
#Configuration
public class HelloWorldConfiguration {
#Bean
public Map<String, String> map() {
return new HashMap<>();
}
// This bean was manually added as a workaround to the #ComponentScan problem
#Bean
public HelloWorldMessageService helloWorldMessageService() {
return new HelloWorldMessageService();
}
// This bean was manually added as a workaround to the #ComponentScan problem
#Bean
public HelloWorldRs helloWorldRs() {
return new HelloWorldRs();
}
}
First, I'd recommend to use a newer #RunWith(SpringRunner.class) but that makes no difference, it is just shorter (and recommended).
Second, from the #EnableAutoConfiguration I see that you are using spring boot - which is certainly a good thing. There are some good reasons why not to use #ComponentScan directly. Can you try the following?
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes=YourApplication_or_other_Configuration.class)
public class HelloWorldTest {
... etc.
I don't know if this will turn out to be the solution, but don't use the default package (i.e. don't put *.java in "src/main/java" directly), and definitely don't use a #ComponentScan or #EnableAutoConfiguration in the default package. You will end up killing your application on startup as it tries to scan everything on the classpath (including all the Spring libraries).
SpringBoot 2.7.3, JUnit 5.8.2
If you want to have full control about the spring's configuration (and not rely on the hidden magic of auto configuration) I suggest to create an explicit configuration class:
#ComponentScan(basePackages = { "my.package.to.scan" })
public class MySpringTestConfig
{
// just for spring configuration annotations
}
and reference it in your test class:
#ContextConfiguration(classes = { MySpringTestConfig.class })
#ExtendWith({ SpringExtension.class })
class MySpringTest
{
...
}

Categories