Spring Boot test with yaml properties by profile - java

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());
}
}

Related

Spring Boot - How to read properties from multiple custom yml

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

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");
}
},

Exclude a particular class from Spring Component scan while writing Spring Integration Test

My classes are..
lies in src/intregation-test/java
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = StoreOrderFulfillmentApplication.class)
#ActiveProfiles("Test")
public class OrderCreationIntregationTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private OrderRepository orderRepository;
#MockBean
private OrderLineItemRepository orderLineItemRepository;
#MockBean
private InternalEventPublisher internalEventPublisher;
#SuppressWarnings("unchecked")
#Before
public void setup() {
Mockito.when(orderRepository.findByOfsReferenceId("OFS:GMO:Z100002062-99")).thenReturn(null);
OrderEntity savedOrder = new OrderEntity();
savedOrder.setOrderId(1023);
Mockito.when(orderRepository.save(Mockito.any(OrderEntity.class))).thenReturn(savedOrder);
Iterable<OrderLineItemEntity> orderLineItemList = prepareOrderLineItemEntityIterable();
Mockito.when(orderLineItemRepository.save(Mockito.any(Iterable.class))).thenReturn(orderLineItemList);
}
#Test
public void test() throws ParseException {
FulfillmentOrder fulfillmentOrderRequestVO = new FulfillmentOrder();
fulfillmentOrderRequestVO = buildFulfillmentOrder();
String myMessage = "Order Created";
ResponseEntity<ResponseOrderMessage> responseEntity = restTemplate.postForEntity("/fulfillmentprocessor/orders",
fulfillmentOrderRequestVO, ResponseOrderMessage.class);
ResponseOrderMessage responseOrderMessage = responseEntity.getBody();
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals(myMessage, responseOrderMessage.getMessage());
}
lies in src/main/java
#SpringBootApplication
public class StoreOrderFulfillmentApplication {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplication.class, args);
}
}
Now the problem is I wanted to exclude a class
from being get component scanned.my this class contains the dependency for apache Kafka.
if this class loads while container start up it start looking for kafka running instances.
so while running Intregation test I will not be starting my Kafka server,so I wanted to run
Intregation test making kafka shutdown.
This I can achieved by adding one line code in StoreOrderFulfillmentApplication class
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
by addding this line of code StoreOrderFulfillmentApplication class it is excluding OrderReceiveEventConfiguration class from being get component scanned.
now the problem is I not suppose add any test configuration changes in the main code.
so I am struggling to do the same exclusion from src/intregation-test/java source folder, is their some way that I can exclude this particular class during container startup code.
but it should not affect my main class code means code inside src/main/java
Any help is Appreciated..
You can make use of #Conditional as shown below.
In application.properties introduce a property say kafka.enabled.
Annotate the OrderReceiveEventConfiguration with #Conditional(PropertyCondition.class)
Depending on kafka.enabled value viz. true (for normal run) or false (for testing) the OrderReceiveEventConfiguration will be picked up or ignored respectively without changing the code.
Let know in comments in case any more information is required.
Except main #conditional annotation there are set of similar annotation to be used for different cases.
Class conditions
The #ConditionalOnClass and #ConditionalOnMissingClass annotations allows configuration to be included based on the presence or absence of specific classes.
E.g. when OObjectDatabaseTx.class is added to dependencies and there is no OrientWebConfigurer bean we create the configurer.
#Bean
#ConditionalOnWebApplication
#ConditionalOnClass(OObjectDatabaseTx.class)
#ConditionalOnMissingBean(OrientWebConfigurer.class)
public OrientWebConfigurer orientWebConfigurer() {
return new OrientWebConfigurer();
}
Bean conditions
The #ConditionalOnBean and #ConditionalOnMissingBean annotations allow a bean to be included based on the presence or absence of specific beans. You can use the value attribute to specify beans by type, or name to specify beans by name. The search attribute allows you to limit the ApplicationContext hierarchy that should be considered when searching for beans.
See the example above when we check whether there is no defined bean.
Property conditions
The #ConditionalOnProperty annotation allows configuration to be included based on a Spring Environment property. Use the prefix and name attributes to specify the property that should be checked. By default any property that exists and is not equal to false will be matched. You can also create more advanced checks using the havingValue and matchIfMissing attributes.
#ConditionalOnProperty(value='somebean.enabled', matchIfMissing = true, havingValue="yes")
#Bean
public SomeBean someBean(){
}
Resource conditions
The #ConditionalOnResource annotation allows configuration to be included only when a specific resource is present.
#ConditionalOnResource(resources = "classpath:init-db.sql")
Web application conditions
The #ConditionalOnWebApplication and #ConditionalOnNotWebApplication annotations allow configuration to be included depending on whether the application is a 'web application'.
#Configuration
#ConditionalOnWebApplication
public class MyWebMvcAutoConfiguration {...}
SpEL expression conditions
The #ConditionalOnExpression annotation allows configuration to be included based on the result of a SpEL expression.
#ConditionalOnExpression("${rest.security.enabled}==false")
You should able able to create a separate config class in your test package
#SpringBootApplication
#ActiveProfiles("Test")
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
public class StoreOrderFulfillmentApplicationTest {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplicationTest.class, args);
}
}
And then in your test class
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = StoreOrderFulfillmentApplicationTest.class)
#ActiveProfiles("Test")
public class OrderCreationIntregationTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private OrderRepository orderRepository;
#MockBean
private OrderLineItemRepository orderLineItemRepository;
#MockBean
private InternalEventPublisher internalEventPublisher;
#SuppressWarnings("unchecked")
#Before
public void setup() {
Mockito.when(orderRepository.findByOfsReferenceId("OFS:GMO:Z100002062-99")).thenReturn(null);
OrderEntity savedOrder = new OrderEntity();
savedOrder.setOrderId(1023);
Mockito.when(orderRepository.save(Mockito.any(OrderEntity.class))).thenReturn(savedOrder);
Iterable<OrderLineItemEntity> orderLineItemList = prepareOrderLineItemEntityIterable();
Mockito.when(orderLineItemRepository.save(Mockito.any(Iterable.class))).thenReturn(orderLineItemList);
}
#Test
public void test() throws ParseException {
FulfillmentOrder fulfillmentOrderRequestVO = new FulfillmentOrder();
fulfillmentOrderRequestVO = buildFulfillmentOrder();
String myMessage = "Order Created";
ResponseEntity<ResponseOrderMessage> responseEntity = restTemplate.postForEntity("/fulfillmentprocessor/orders",
fulfillmentOrderRequestVO, ResponseOrderMessage.class);
ResponseOrderMessage responseOrderMessage = responseEntity.getBody();
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals(myMessage, responseOrderMessage.getMessage());
}
Create a test application class
#SpringBootApplication
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
public class TestStoreOrderFulfillmentApplication {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplication.class, args);
}
}
Add the following configuration annotation to you test class
#SpringApplicationConfiguration(classes = TestStoreOrderFulfillmentApplication .class)

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