junit spring boot profile properties not being read - java

I am trring to read a value from the properties file in my junit setup using spring boot.
I can not read the value. Below is my content:-
application-test.properties
my.user.name=Amar
COnfig file to create beans:
#Configuration
#ActiveProfiles("test")
#Profile("test")
public class RdbmsTestConfig {
#Value("${my.user.name}")
private String name;
#Bean
public String myString(){
return "Amar";
}
#Bean
public PropsHolder propsHolder() {
PropsHolder propsHolder = new PropsHolder();
propsHolder.setUserName(name);
return propsHolder;
}
}
My test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = RdbmsTestConfig.class)
#ActiveProfiles("test")
public class TestRoomService {
#Autowired
#Qualifier("myString")
private String myString;
#Autowired
private PropsHolder propsHolder;
#Autowired
private Environment env;
#Test
public void userTest() {
Arrays.stream(env.getActiveProfiles()).forEach(System.out::println);
System.out.println(propsHolder.getUserName());
Assert.assertNotNull(myString);
Assert.assertEquals("Amar",myString);
}
}
The value for propsHolder.getUserName comes out to be ${my.user.name}

First remove #ActiveProfiles("test") from your class RdbmsTestConfig. Then your test just defines the RdbmsTestConfig as spring context. As I can see you do not run a real spring boot test. The problem is you do not have any PropertyPlaceholderConfigurer configured in your spring config. So either configure one PropertyPlaceholderConfigurer or add #SpringBootTest to your test if you have any SpringBootApplication.

I've never used #Profile(), so i'm not sure if that is supposed to do what you want it to do. But I'm always using #PropertySources(), because otherwise, how is the code supposed to know where to look for the properties?
#Configuration
#PropertySources(value = { #PropertySource("classpath:core-
test.properties") })

I created a base test class that has the required annotations:-
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = RdbmsTestConfig.class)
#ActiveProfiles("test")
#SpringBootTest
public abstract class BastTest { }
#ActiveProfiles set the profile to use used, I dont have to mention it in the application.properties file
My test class now extends this:-
public class TestRoomService extends BastTest
In my RdbmsTestConfig remove #ActiveProfiles annotation.

Related

Testing #ConfigurationProperties annotation without loading Spring context

I'm trying to test a class that has the #ConfigurationProperties annotation but without loading the entire Spring context. I tried using only the JUnit5's features in order to do that but until now didn't succeed in that.
I'm using spring-boot-starter-parent v2.6.2 .
The class I'm testing :
#ConfigurationProperties("db.mongo")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class MongoProperties {
private String host;
private String db;
private String user;
private String password;
}
The application.yaml :
db:
mongo:
host: localhost
db: test
user: test-user
password: secret
My Test class :
#ExtendWith(SpringExtension.class)
#EnableConfigurationProperties({MongoProperties.class})
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Test
public void mongoPropertiesLoadedTest(){
assertNotNull(properties.getDb());
assertNotNull(properties.getHost());
assertNotNull(properties.getPassword());
assertNotNull(properties.getUser());
}
}
The MongoProperties bean is injected successfully, but all the values inside are null and the asserts fail.
Adding the #SpringBootTest solves the issue of the null values in the instance of the bean, but it also starts the whole spring context which is what I don't want.
But using #ExtendWith(SpringExtension.class) will also start the spring context. The difference is that it starts the context in a traditional way but #SpringBootTest starts it in a spring-boot way. So no matter you use which of them , it still requires to start the spring context.
If your concern is to minimise the number of beans required to be loaded into the spring context when using #SpringBootTest, you can actually configure a specified #Configuration like the following as by default #SpringBootTest will load all beans defined in your applications which may be too much for testing (see this for details) :
#SpringBootTest
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
If you really want to just use #ExtendWith(SpringExtension.class) , you will lose the spring-boot feature such as externalising configuration features which cause you cannot load properties from application.properties and cannot support loading properties from YAML file etc. You have to manually configure ConfigDataApplicationContextInitializer to enable such features :
#ExtendWith(SpringExtension.class)
#ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
You can consider to further use #SpringJUnitConfig to combine #ExtendWith(SpringExtension.class) and #ContextConfiguration together which gives you :
#SpringJUnitConfig(initializers = ConfigDataApplicationContextInitializer.class)
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
Actually both approaches do not have much differences in term of speed , so I prefer to just use #SpringBootTest for simplicity as it does not requires you to configure ConfigDataApplicationContextInitializer.

Spring JUnit5 test not loading resource values

I know there are a lot of questions regarding this, but all of them are suggesting to use #TestPropertySource and #EnableConfigurationProperties. I have already used them but still not working.
Config class - src/main/java/com/demo/config/AppConfig.java
#Configuration
#ConfigurationProperties(prefix = "api")
#Getter
#Setter
public class AppConfig {
private List<String> providers;
private boolean enabled;
}
Property source - src/test/resources/application-test.yml
api:
enabled: true
providers:
- prov1
- prov2
Test class - src/test/../MyTest.java
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = MyTestConfiguration.class)
class MyTest {
#Autowired
private AppConfig appConfig;
#Test
void runTest() {//some code with breakpoint}
}
Test configuraton - src/test/.../MyTestConfiguration.java
#TestConfiguration
#TestPropertySource(locations = "classpath:application-test.yml")
#EnableConfigurationProperties(value = AppConfig.class)
#ActiveProfiles("test")
public class MyTestConfiguration {
}
When I run the test, runTest() and inspect autowired appConfig value, the providers are empty and enabled is false. That means the values in yml file were not loaded.
I found a similar kind of question, but without answer.
I modified MyTest as #sergey-tsypanov suggested and then deleted MyTestConfiguration class. It worked and appConfig has values.
#SpringBootTest(classes = AppConfig.class)
#EnableAutoConfiguration
#ActiveProfiles("test")
class MyTest {
#Autowired
private AppConfig appConfig;
#Test
void runTest() {//some code with breakpoint}
}
It seems even if I don't have #SpringBootApplication, I can use #SpringBootTest and #EnableAutoConfiguration. I had spring Boot dependency in pom.xml
I think you need to replace
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = MyTestConfiguration.class)
with
#SpringBootTest(classes = {MyTestConfiguration.class})
Then application-test.yml will be picked up automatically.

SpringJUnitConfig for mulitple junit classes

In our project, every Junit class(which is annotated using SpringJunitConfig) is having a #Configuration annotated class, which creates the Bean which is required to test any particular Test-class method.
Example:
#SpringJunitConfig
class TestClass {
#Configuration
class TestConfig {
#Bean
public TestClass testClass(DependantBean dependantBean) {
return new TestClass(dependantBean);
}
#Bean
public DependantBean dependantBean() {
return new DependantBean();
}
}
#Autowire private TestClass testClass;
#Test
void testMethod() {
//do testing
}
}
However this looks handy for a single test class, but the issue is every test class is having its own configuration class, which we are trying to avoid and I wanted to have one single configuration class for my whole test classes. Can someone help me to remove this repeated #Configuration?
Thanks in advance.
You could easily create a Configuration meant only for Test cases, and it could be used in #SpringJunitConfig:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/junit/jupiter/SpringJUnitConfig.html
#SpringJUnitConfig is a composed annotation that combines
#ExtendWith(SpringExtension.class) from JUnit Jupiter with
#ContextConfiguration from the Spring TestContext Framework.
Define your TestConfig:
#Configuration
public class ValidationTestSpringConfig {
#Bean
public TaskScheduler validationTaskScheduler() {
ThreadPoolTaskScheduler tpts = new ThreadPoolTaskScheduler();
tpts.setPoolSize(2);
return tpts;
}
}
Then using this #SpringJunitConfig annotation, you can actually provide the context configuration you need, which loads up the Test Beans:
#SpringJUnitConfig(ValidationTestSpringConfig.class)
public class HttpValidationIntegrationTest {
#Autowired
private TaskScheduler taskScheduler;
}

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

#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