I have a spring boot app and have written unit tests using a postgres test container (https://www.testcontainers.org/) and JUnit. The tests have the #SpringBootTest annotation which loads the context and starts up a test container before running the test.
Loading the context and starting the container takes around 15sec on my relatively old Macbook, but the test themselves are pretty fast (< 100ms each). So in a full build with 100s of tests, this does not really matter. It is a one time cost of 15sec.
But developing/debugging the tests individually in an IDE becomes very slow. Every single test incurs a 15 sec startup cost.
I know IntelliJ and Springboot support hot reload of classes when the app is running. Are there similar solutions/suggestions for doing the same for unit tests ? i.e Keep the context loaded and the testcontainer(DB) running but recompile just the modified test class and run the selected test again .
There is a simple solution for your issue I believe. You haven't specified how exactly do you run the test container in the test, however I have a successful experience with the following approach:
For tests running locally - start postgres server on your laptop once (say at the beginning of your working day or something). It can be dockerized process or even regular postgresql installation.
During the test spring boot application doesn't really know that it interacts with test container - it gets host/port/credentials and that's it - it creates a DataSource out of these parameters.
So for your local development, you can modify the integration with the test container so that the actual test container will be launched only if there is no "LOCAL.TEST.MODE" env. variable defined (basically you can pick any name - it's not something that exists).
Then, define the ENV variable on your laptop (or you can use system property for that - whatever works for you better) and then configure spring boot's datasource to get the properties of your local installation if that system property is defined:
In a nutshell, it can be something like:
#Configuration
#ConditionalOnProperty(name = "test.local.mode", havingValue = "true", matchIfMissing = false)
public class MyDbConfig {
#Bean
public DataSource dataSource () {
// create a data source initialized with local credentials
}
}
Of course, more "clever" solution with configuration properties can be implemented, it all depends on how do you integrate with test containers and where do the actual properties for the data source initialization come from, but the idea will remain the same:
In your local env. you'll actually work with a locally installed PostgreSQL server and won't even start the test container
Since all the operations in postgresql including DDL are transactional, you can put a #Transactional annotation on the test and spring will roll back all the changes done by the test so that the DB won't be full of garbage data.
As opposed to Test containers, this method has one significant advantage:
If your test fails and some data remains in the database you can check that locally because the server will remain alive. So you'll be able to connect to the db with PG Admin or something and examine the state...
Update 1
Based on op's comment
I see what you say, Basically, you've mentioned two different issues that I'll try to refer to separately
Issue 1 Application Context takes about 10-12 seconds to start.
Ok, this is something that requires investigation. The chances are that there is some bean that gets initialized slowly. So you should understand why exactly does the application starts so slowly:
The code of Spring (scanning, bean definition population, etc) works for particles of a second and usually is not a bottleneck by itself - it must be somewhere in your application.
Checking the beans startup time is kind of out of scope for this question, although there are certainly methods to do so, for example:
see this thread and for newer spring versions and if you use actuator this here. So I'll assume you will figure out one way or another why does it start slowly
Anyway, what you can do with this kind of information, and how you can make the application context loading process faster?
Well, obviously you can exclude the slow bean/set of beans from the configuration, maybe you don't need it at all in the tests or at least can use #MockBean instead - this highly varies depending on the actual use case.
Its also possible to provide configuration in some cases that will still load that slow bean but will alter its behavior so that it won't become slow.
I can also point of "generally applicable ideas" that can help regardless your actual code base.
First of all, if you're running different test cases (multi-select tests in the IDE and run them all at once) that share exactly the same configurations, then spring boot is smart enough to not re-initialize the application context. This is called "caching of the application context in cache". Here is one of the numerous tutorials about this topic.
Another approach is using lazy beans initialization. In spring 2.2+ there is a property for that
spring:
main:
lazy-initialization: true
Of course, if you're not planning to use it in production, define it in src/test/resource's configuration file of your choice. spring-boot will read it as well during the test as long as it adheres to the naming convention. If you have technical issues with this. (again out of scope of the question), then consider reading this tutorial
If your spring boot is older than 2.2 you can try to do that "manually": here is how
The last direction I would like to mention is - reconsidering your test implementation. This is especially relevant if you have a big project to test. Usually, the application has separation onto layers, like services, DAO-s, controllers, you know. My point is that the testing that involves DB should be used only for the DAO's layer - this is where you test your SQL queries.
The Business logic code usually doesn't require DB connection and in general, can be covered in unit tests that do not use spring at all. So instead of using #SpringBootTest annotation that starts the whole application context, you can run only the configuration of DAO(s), the chances that this will start way faster and "slow beans" belong to other parts of the application. Spring boot even has a special annotation for it (they have annotations for everything ;) ) #DataJpaTest.
This is based on the idea that the whole spring testing package is intended for integration tests only, in general, the test where you start spring is the integration test, and you'll probably prefer to work with unit tests wherever possible because they're way faster and do not use external dependencies: databases, remote services, etc.
The second issue: the schema often goes out of sync
In my current approach, the test container starts up, liquibase applies my schema and then the test is executed. Everything gets done from within the IDE, which is a bit more convenient.
I admit I haven't worked with liquibase, we've used flyway instead but I believe the answer will be the same.
In a nutshell - this will keep working like that and you don't need to change anything.
I'll explain.
Liquibase is supposed to start along with spring application context and it should apply the migrations, that's true. But before actually applying the migrations it should check whether the migrations are already applied and if the DB is in-sync it will do nothing. Flyway maintains a table in the DB for that purpose, I'm sure liquibase uses a similar mechanism.
So as long as you're not creating tables or something that test, you should be good to go:
Assuming, you're starting the Postgres server for the first time, the first test you run "at the beginning of your working day", following the aforementioned use-case will create a schema and deploy all the tables, indices, etc. with the help of liquibase migrations, and then will start the test.
However, now when you're starting the second test - the migrations will already be applied. It's equivalent to the restarting of the application itself in a non-test scenario (staging, production whatever) - the restart itself won't really apply all the migration to the DB. The same goes here...
Ok, that's the easy case, but you probably populate the data inside the tests (well, you should be ;) ) That's why I've mentioned that it's necessary to put #Transactional annotation on the test itself in the original answer.
This annotation creates a transaction before running all the code in the test and artificially rolls it back - read, removes all the data populated in the test, despite the fact that the test has passed
Now to make it more complicated, what if you create tables, alter columns on existing tables inside the test? Well, this alone will make your liquibase crazy even for production scenarios, so probably you shouldn't do that, but again putting #Transactional on the test itself helps here, because PostgreSQL's DDLs (just to clarify DDL = Data Definition Language, so I mean commands like ALTER TABLE, basically anything that changes an existing schema) commands also transactional. I know that Oracle for example didn't run DDL commands in a transaction, but things might have changed since then.
I don't think you can keep the context loaded.
What you can do is activate reusable containers feature from testcontainers. It prevents container's destruction after test is ran.
You'll have to make sure, that your tests are idempotent, or that they remove all the changes, made to container, after completion.
In short, you should add .withReuse(true) to your container definition and add testcontainers.reuse.enable=true to ~/.testcontainers.properties (this is a file in your homedir)
Here's how I define my testcontainer to test my code with Oracle.
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.OracleContainer;
public class StaticOracleContainer {
public static OracleContainer getContainer() {
return LazyOracleContainer.ORACLE_CONTAINER;
}
private static class LazyOracleContainer {
private static final OracleContainer ORACLE_CONTAINER = makeContainer();
private static OracleContainer makeContainer() {
final OracleContainer container = new OracleContainer()
// Username which testcontainers is going to use
// to find out if container is up and running
.withUsername("SYSTEM")
// Password which testcontainers is going to use
// to find out if container is up and running
.withPassword("123")
// Tell testcontainers, that those ports should
// be mapped to external ports
.withExposedPorts(1521, 5500)
// Oracle database is not going to start if less
// than 1gb of shared memory is available, so this is necessary
.withSharedMemorySize(2147483648L)
// This the same as giving the container
// -v /path/to/init_db.sql:/u01/app/oracle/scripts/startup/init_db.sql
// Oracle will execute init_db.sql, after container is started
.withClasspathResourceMapping("init_db.sql"
, "/u01/app/oracle/scripts/startup/init_db.sql"
, BindMode.READ_ONLY)
// Do not destroy container
.withReuse(true)
;
container.start();
return container;
}
}
}
As you can see this is a singleton. I need it to control testcontainers lifecycle manually, so that I could use reusable containers
If you want to know how to use this singleton to add Oracle to Spring test context, you can look at my example of using testcontainers. https://github.com/poxu/testcontainers-spring-demo
There's one problem with this approach though. Testcontainers is not going to stop reusable container ever. You have to stop and destroy the container manually.
I can't imagine some hot reload magic flag for testing - there is just so much stuff that can dirty the spring context, dirty the database etc.
In my opinion the easiest thing to do here is to locally replace test container initializer with manual container start and change the properties for the database to point to this container. If you want some automation for this - you could add before launch script (if you are using IntelliJ...) to do something like that: docker start postgres || docker run postgres (linux), which will start the container if its not running and do nothing if it is running.
Usually IDE recompiles just change affected classes anyway and Spring context probably wont start for 15 secs without a container starting, unless you have a lot of beans to configure as well...
I'm trying to learn testing with Spring Boot, so sorry if this answer is not relevant.
I came across this video that suggests a combination of (in order of most to least used):
Mockito unit tests with the #Mock annotation, with no Spring context when it's possible
Slice tests using the #WebMvcTest annotation, when you want to involve some Spring context
Integration tests with #SpringBootTest annotation, when you want to involve the entire Spring Context
I have multiple Maven integration tests that are updating the the state of the database, which could create conflicts between these tests. I was wondering if there is a way to isolate these integration tests by leveraging Maven phases or any other approach? Ideally, I would like to have a way to run database migrations before every integration test class. I am using Flyway as the migration tool for my PostgreSQL database and I am using JUnit 4.12. The migrations that I am running are basically creating and populating tables with data for testing.
Junit has #Before and #After annotations to let it invoke methods before and after each test class.
Those methods are then responsible for bringing the database into a known state before each test.
Responsibilities of Maven is to run tests one by one on integration-tests phase, and check results on verify. It also able to prepare/shutdown environment. Check failsafe plugin.
And all isolation between tests is a responsibility of a test framework you use (JUnit, TestNG, Cucumber, etc.).
I was able to solve this using flyway-core. Basically I ended up doing the following inside each of the test classes:
#BeforeClass
public static void migrateDB(){
Flyway flyway = Flyway.configure().dataSource(url, user, password).load();
flyway.clean();
flyway.migrate();
}
I'm building a REST service using Spring boot framework (v2.0.2), where I have integration tests running on a database. I googled a lot and there is a sea of articles about cleaning database before tests, but unfortunately I find them either inefficient or a hack and not for Spring boot. Could you, please, bear with me and suggest a good way for this problem?
Ideally, I think the database should be cleared not before each test, but before some group of them, like suite or maybe each test class. One of the found suggestions looks like this:
#Autowired
protected Flyway flyway;
#Before
public void init() {
flyway.clean();
flyway.migrate();
}
which rebuilds database before each test and clearly is not efficient. Changing this to static context and using #BeforeClass does not work as Spring does not inject static fields.
Is there some nice way to reach this flyway bean from static context, to make this solution work?
Sub-question here: Flyway has a command clean, which not only clears data, but drops everything and then migrate command performs migrations again. This also seems like overhead. As the migrations are checked at startup anyway, I don't see the necessity to tear down and rebuild everything before each test group. Just clearing data would be sufficient. Would you give some advice about how this can be achieved?
To sum up, I'm looking for a standard way of removing the database data (and not tables if possible) before each group of integration tests (e.g. per class). I suppose everybody faces this task while using Spring boot, so maybe there is some nice solution considered in the framework itself.
Thank you!
You could create configuration file for your tests. It would run one time before all tests.
#Configuration
public class TestConfig {
#Bean
public FlywayMigrationStrategy clean() {
return flyway -> {
flyway.clean();
flyway.migrate();
};
}
}
This answer was useful but it didn't get me all the way there and so I thought I'd come back and add an answer in case someone else was looking to solve this same issue. The bean definition above was terrific.
There are spring profiles of which there are 5 or so possibilities. I looked at the docs and how people use them but went another route. Maven has 6 scopes, but the ones which are useful in this case are runtime and test.
As I dug into the spring profiles and the various ways one can switch between them it seemed for my situation a bit too complex. I just want my database under test to be created, scructured and populated with some data so I can test the repositories in my jpa spring boot app. I don't want to spend 4 hours setting up profiles. Not that it isn't a worthy endeavor in the long run, just that I wanted to get things moving.
When I execute spring-boot:run, I want the non-test db to be migrated but I don't want any crud data in there that I use for testing.
So, in the live app I want a virtually empty database and during tests, I want flyway to clean the db, run the versioned migrations and populate it with test data.
The answer above led me to a solution which I will probably fold into spring profiles as my project gets closer to production.
Turns out that spring-boot-test provides a #TestConfiguration annotation which you can attach to any class in the src/test/ hierarchy. I created a FlywayConfiguration class which contains the bean definition provided above:
package com.foo.fooservice;
import org.flywaydb.core.Flyway;
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
#TestConfiguration
public class FlywayMigrationConfig {
#Bean
public static FlywayMigrationStrategy cleanMigrateStrategy(){
return flyway -> {
flyway.clean();
flyway.migrate();
};
}
}
So now, if I want to use this in a test, I add another nifty annotation in the appropriate test class- #Includes, a companion to the #TestConfiguration annotation - so that I can use this configuration in the same way I might have used #BeforeClass like so:
#DataJpaTest
#Import(FlywayMigrationConfig.class)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class AccountUserRepoTest {
#Autowired
private AccountUserRepo accountUserRepo;
#Autowired
private FlywayMigrationStrategy strategy;
This allows me to inject this flyway migration strategy on a per-test-class basis. Spring won't auto-inject your bean into every test class and you can use this configuration now just by adding the #Includes annotation to the appropriate test classes. You don't have to define the bean in every test class where you want to use it. Just use #Includes(your#TestCongiguration-annoted-class).
I happen to be using postgresSQL as opposed to H2 because I figure if I'm doing an integration test on the repository entities I might as well do it against what I will be using in production.
Also: src/main/resources has the jdbc and flyway properties set to a dev schema name and jdbc url.
src/test/resources/application.properties sets the schema name to 'test' (you can name it whatever you want).
The one drawback to this approach that you may not want is the granularity- the DB is cleaned and repopulated for every test class which you configure this way.
I personally like this because for each repository class I'm testing I'd like the data to be refreshed. I also like that if I'm working on a particular test class, having the configuration at this level of granularity means that 'run test' works out of the box. No special configuration in the IDE is required to make it work.
I have written unit tests for a third party rest API. These tests are what I would call live tests in that they do test the rest api responses and require valid credentials. This is required because the documentation provided by the third party is not up-to-date so its the only way of knowing what the response will be. Obviously, I can't use these as the unit tests because they actually connect externally. Where would be a good place to put these tests or separate them from mocked unit tests?
I have currently had to comment them out when I check them in so that they don't get run by the build process.
I tend to use assumeTrue for these sort of tests and pass a system property to the tests. So the start of one of your tests would be:
#Test
public void remoteRestTest()
{
assumeTrue(System.getProperty("run.rest.tests").equals("true"));
...
}
This will only allow the test to run if you pass -Drun.rest.tests=true to your build.
What you are looking for are integration tests. While the scope of a unit test is usually a single class the scope of an integration test is a whole component in its environment and this includes the availability of external resources such as your remove REST service. Yes, you should definitely keep integration test separate from unit tests. How this can be done in your environment depends on your build process.
For instance, in case you work with Maven there is a Maven Failsafe Plugin that targets integration testing in your build process.
Question: Is there a common way to validate if a (xml based) spring configuration is valid?
Further explanation:
With "valid" I mean not if the xml itself is valid (I don't talk about xsd validation), I mean more "logical valid", e.g. if all referenced classes are available, or if a specific reference is available / could be resolved.
The background of this question is a QA-process within a CI-environment for a spring-mvc application:
Assuming a developer have a typo in a class name, or a reference is not unique in a webcontext configuration file, and he commits this change. Now an automated build is triggered:
The application compiles successfully
unit tests are "green" (since they don't need any spring configuration)
integration tests are "green" (since integration tests does not rely on webcontext configurations)
functional / regression testing starts
In current setup we would note this simple typo in step 4 - but it is quite time consuming to reach this point.
It would be great to have a mechanism / tool which can validate if a spring context could be loaded in order to save some time.
integration tests does not rely on webcontext configurations
My integration tests run against the deployed application. If you want to test the web context then... test it. Specifics depends on what you want to test.
You can use Maven or whatever build tool you have to deploy your app in a test environment and run the integration tests afterwards.
You can use a lightweight server like Grizzly, reuse it across test classes etc.
Since those tests are costly I usually have one that checks the app can be deployed and the context starts. I use Grizzly and run this test along the rest of the Unit tests so issues are detected asap. Other similar test cases can be added depending on the situation.
A simple way to "pre-validate" the XML configuration before integration testing would be to use a JUnit test using SpringJUnit4ClassRunner. This is available in the spring-test JAR.
e.g.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "/foo.xml" })
public class XmlValidate {
#Test
public void loadsWithoutError() {
// nothing to do, SpringJUnit4ClassRunner will throw exception
// if there are any class-not-found exceptions etc.
}
}