Spring Boot integration test with #PostConstruct depends on #Before section - java

I write integration test for SpringBoot application with opportunity to run multiple tests simultaneously they have file system dependencies that's why I need to create unique root folder for each integration test.
I have a spring bean in production app that have #PostConstruct section to perform long-running operations. These long-running operations rely on file system structure that I prepare in #Before section of unit-tests.
Unique folders for simultaneously running tests are set via root.directory=target/results/#{T(java.util.UUID).randomUUID().toString()}. This property is injected as #Value in spring #Component class to avoid re-calculation root directory in different places.
The main issue is following: I need to prepare folders which name should be specified via application.properties with some resources(copy files, folders) in #Before test-section and run #PostConstruct only after all resource are prepared.
I tried several variants: 1) autowire in the test a bean with #PostConstruct and invoke it programmatically in the end of #Before - it's a single working case and it looks strange and fragile
2) Replace #PostConstruct with InitializingBean and afterPropertiesSet - it doesn't work. Because I have a value for folder name on bean initialization stage but without copied resources that I copy in #Before test section
I hope I explained well. I will be appreciate with any help or advice.

Your Question are bit vague but to answer the issues.
The main issue is following: I need to prepare folders which name should be specified via application.properties with some resources(copy files, folders) in #Before test-section and run #PostConstruct only after all resource are prepared.
Make Sure you are running Test
#RunWith(SpringRunner.class)
#SpringBootTest(properties = "myFileName=test.txt")
In test #Before use
#Rule
public final TemporaryFolder temporaryFolder = new TemporaryFolder();
temporayFolder.newFile( use #Value here)
So #Before will run before any spring boot startup and then call your function in one of the unit test methods

Related

Spring Boot Property file precedence

In my Spring Boot program I'm getting a failure due to a bad property value on load. In particular, it uses the DB2 hibernate dialect but it's not defined in the property file I thought I was using.
Assuming no annotations, where does Spring look for the properties file? Yes I know it normally resides in src/main/resources/application.properties
What if I have a property in my test cases; does it ignore the one in main and use the one in test? Or does it start with the main version and let the test one override the main where it applies?
Does the application profile affect the property file used? Some people use the same application.properties file name in both main and test.
If I do have a TestSource annotation with a class path location, does it still augment it with something somewhere else?
Finally, how can I get Spring to tell me everywhere it looked for properties and not just one of them?
#Woodsman
It's possible to use many settings in each profile/environment in spring application on src/main/resources/ folder
application-dev.properties
application-local.properties
application-onlytests.properties
application-prd.properties
application.properties
the name after hyphen will be the name profile got by spring
Ex: application-{myenviroment}.properties
When you start spring application will be used application.properties by default.
You can tell spring to use an specific properties passing the environment in one of ways below:
Putting spring.profiles.active=prd inside application.properties file
Passing by parameters when start spring app --spring.profiles.active=local
Running your jar on command line java -jar myjar.jar --spring.profiles.active=dev
Setting an environment var in your machine/docker/container SET SPRING_ACTIVES_PROFILE=local
There are other ways using annotations on beans, passing jvm arguments and others
If you need run your tests in a specific configuration ( vars, database, settings ), It's possible to pass which .properties will be used
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
#SpringBootTest
#TestPropertySource(locations = "classpath:application-onlytests.properties")
public class RunTest_from_onlytests_properties {
#Autowired
private MockMvc mockMvc;
#Test // org.junit.jupiter.api.Test
public void test() throws Exception{
// ...
}
}

How to inject Beans inside a test class properly with spring boot and annotations

I have a class I want to test if it uses the right Beans when a certain profile is active. Therefore I have written a test class, with the profile active, for the DomesticService (which in turn uses the GardeningService and CleaningService, all of it is autowired).
#Component
public class HumanDomesticService implements DomesticService {
private CleaningService cleaningService;
private GardeningService gardeningService;
private Logger logger;
HumanDomesticService() {
}
#Autowired
public HumanDomesticService(CleaningService cleaningService, GardeningService gardeningService, Logger logger) {
setCleaningService(cleaningService);
setGardeningService(gardeningService);
setLogger(logger);
}
I made a test configuration class, which should scan the whole project for Beans, since the SpringBootApplication annotation includes the ComponentScan annotation.
#SpringBootApplication
public class ActiveProfileConfig {
}
Yet my test class can't seem to find the right Beans to complete the test.
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'be.mycompany.springlessons.housekeeping.domestic.service.ActiveProfileSmallHouseTest': Unsatisfied dependency expressed through field 'service'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'be.mycompany.springlessons.housekeeping.domestic.service.DomesticService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
After that I tried to make Beans inside my configuration class, which makes my tests succeed, but then Maven complains about finding 2 Beans which could be injected there. Maven seems to be able to see all the beans through the ComponentScan.
Finally I ended importing the necessary classes, which works for both my tests and Maven, but it just doesn't seem to be the right and best solution.
#SpringBootTest(classes = ActiveProfileConfig.class)
#ActiveProfiles(profiles = "smallHouse")
#Import({HumanDomesticService.class, HumanCleaningService.class, RobotCleaningService.class, HumanGardeningService.class, HedgeTrimmerFactory.class, Broom.class, VacuumCleaner.class,
Sponge.class, DisposableDuster.class, LawnMower.class, LoggerFactory.class})
public class ActiveProfileSmallHouseTest {
#Autowired
private DomesticService service;
I have tried to search on the internet for another solution and saw I wasn't the only one with the problem, but no other solution seemed yet to have worked.
What is the reason ComponentScan doesn't seem to work in a test class and how best to solve this?
I wanted to share our final solution on this.
We both use IntelliJ IDEA as IDE. In this IDE we didn't specify that the tests shouldn't use the module path. IntelliJ now has a setting that lets you enable/disable the usage of the module path for Unit tests.
In maven we already disabled this by configuring the main surefire plugin to not use the module path.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<useModulePath>false</useModulePath>
</configuration>
</plugin>
With this 'new' feature in our IDEA, we also have to do disable this so our tests don't use the class path.
We figured it out because our Maven phase : mvn test
Did run the tests.
Now all the tests run smoothly with just the #SpringBootTest annotation.
To set this in IntelliJ :
in the menu bar : Run > Edit Configurations
in the left menu open the sub menu of Templates > JUnit
uncheck the checkbox next to Use module path
click apply > OK
From now on, all the tests you will run will not use this module path.
If you already have existing tests. Select them win the menu on the left, and uncheck the Use module path option for all of them.
I have had the exact same problem for a long time. I was able to solve this in Spring Boot by adding the main configuration I wanted to use. and all the needed dependencies my test had (All AutoWires) to the classes field of the #SpringBootTest annotation.
#SpringBootTest(classes = {ActiveProfile.class, HumanDomesticService.class, HumanCleaningService.class, RobotCleaningService.class, HumanGardeningService.class, HedgeTrimmerFactory.class, Broom.class, VacuumCleaner.class,
Sponge.class, DisposableDuster.class, LawnMower.class, LoggerFactory.class})
#ActiveProfiles({"smallhouse"})
public class ActiveProfileSmallHouseTest {
#Autowired
private DomesticService service;
...
}
If you weren't using Spring Boot. You could solve this with the #ContextConfiguration annotation. Which works in the same way and is a part of the #SpringBootTest annotation as well.
This is also how it is being done in my main learning source: Pro Spring 5 - Iuliana Cosmina et al - Apress (page 631)
I still don't know why the ComponentScan or automatic loading of the context doesn't work. But at least I know that this way my tests will always run. Also, you are able to run only the classes you need to get this test succeeding, and don't need to fire up the entire context for the test.

Spring Boot + Integration Test

I'm trying to write an integration test for a spring boot project. Unfortunately, I'm confused with the implementation.
Below sample code snippet tried
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MainApplication.class)
#AutoConfigureMockMvc
public class PropertyControllerIT {
#Autowired
private MockMvc mockMvc;
#Test
public void sample_test() throws Exception {
this.mockMvc.perform(post("/property")).andExpect(status().is2xxSuccessful());
}
}
Question
Do I need to have a separate MainClass with
#SpringBootApplication annotated to support the integration test?
Do we create a mock database or override the database configuration. If yes, how do we override the configuration
Should we maintain a separate directory for integration-test similar to java or test like integration-test
You don't need to create any other classes annotated with
SpringBootApplication.
It's a good practice to run integration tests using in memory database for example H2. You can create a new application-test.properties file in resources folder inside of test directory. In order to use override properties you can run test with test profile. In order to run tests with test profile #ActiveProfiles can be used.
You can keep your integration tests in the same test directory like unit test but they can be located in a separate package

Excluding PropertySource from scanning in SpringBoot WebMvcTest or other application context for WebMvcTest

I develop web app with Spring Boot. I have problem with unit test for web layer.
For these tests I'm using annotation #WebMvcTest. Problem is that my main configuration class contains #PropertySource with java arg, which contains path to external properties file, and when I start my unit web test, error is occured that this java arg can't be parsed(of course I can add this java arg to run configuration of my test, but my web unit tests don't need this file).
My main config class:
#SpringBootApplication
#PropertySource(value = {"${pathto.configfile}"})
#EnableAspectJAutoProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
My first solution was to create separate configuration class with "!test" profile, and relocate #PropertySource("..") to it. And to my unit test add #ActiveProfiles("test")
My second Configuration class:
#Configuration
#Profile({"!test"})
#PropertySource(value = {"${pathto.configfile}"})
public class PropertyConfiguration {
}
For unit web test this solution works very good. But new problem appears in starting of my web app. In my external properties file I have
property spring.profiles.active. To this property I assign or db or file value. Depending on its value, apropriate implementation of Repository is created and injected to my service layer. When value is db, app starts good,
but when value is file error is being thrown: NoSuchBeanDefinitionException.
When I come back to my previous version(without second configuration file), app starts good in both cases(but not web unit tests)
So, explain please, why when value of spring.profiles.active=db, app starts good, but when spring.profiles.active=file- failed.And how I can solve my task?
I attempted to find how I can add other application context to my web unit tests, but I didn't find.
Any idea?:)
For my database repositories I'm using Spring Data JPA, so I don't create implementation of these repositories, but I create implementations of my file
repositories, and my implementations had #Profile("file"). After deleting this annotation from implementations, it leaved only on interfaces. I don't know why one config class worked, but two classes didn't. But problem is solved)

Initialising a database before Spring Boot test

I'm using JUnit to test my application and everything works fine as long as the database has been initialised before the testing (using gradle bootRun to run as a web-app). However, if the database is empty, the application does not seem to initialise any models or entities before testing. Is there a way I'm supposed to do this? I made an assumption that the ApplicationRunner class will be ran before the test and initalise the entities. Is there a way to do this or am I using the wrong approach?
This is how my application.properties file is looking like:
server.port=8090
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=123456
server.ssl.key-password 123456
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=create
spring.jpa.hibernate.naming-strategy:org.hibernate.cfg.ImprovedNamingStrategy
application.logger.org.springframework=INFO
My database is stored in /src/main/java/application/persistence/DbConfig.java using a DriverManagerDataSource connection. And I have setup ApplicationRunner to run add a few rows to the db upon starting.
edit:
I should also add that these are the annotations I'm using on the JUnit test file:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes={
AdeyTrackApplication.class,
SecurityConfig.class,
WebConfig.class,
AuthorizationController.class
})
There are various options if you do not want to execute that explicitly from #Before JUnit hook.
Use Spring Boot's JDBC initialization feature, where you would place schema.sql or data.sql into src/test/resources folder, so that it would be picked up only during testing.
Use Spring's #Sql annotation
You can use #Sql annotaion for populate your DB, for example^
#Sql(scripts = "classpath:db/populateDB.sql")
The above answers all use the .sql schema loading technique where I'd have to have a .sql schema for tests. I didn't want to do it that way as my schema would be expanding and I'd rather not go through the hassle of adding entries to the schema as my tests expand.
As I'm using Spring Boot, I came across this annotation which seems to solve the issue by first running bootRun and then running the tests.
In my test annotations I replaced the #ContextConfigurations with #SpringApplicationConfiguration and left all the classes to be the same. This seemed to solve the issue. So now the test task invokes bootRun to load the classes and then runs the tests.
See #SpringApplicationConfiguration
Hop this helps anyone facing the same issue.

Categories