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'm running into a silly situation where I have my flyway dependency defined in a common library's pom file. This common library happens to have Spring Boot integration tests that load the context and so when those integration tests run I get
java.lang.IllegalStateException: Cannot find migrations location in: [classpath:db/migration] (please add migrations or check your Flyway configuration)
I'd rather not duplicate the dependency definition in all of the applications' poms and I can't remove the integration tests. I'd also rather not have a dummy migrations folder. Can I just turn this off somehow in the integration tests?
#SpringBootTest
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#DisableFlywaySomehow
public class MyITest {...}
Use a different profile (although you can also use your current test profile) and just set spring.flyway.enabled = false in your application.{properties,yml} file.
You can also play with the #TestPropertySource annotation on a test-by-test case.
To be fair, your integration tests should operate on whatever state your app is "current". Which also means (if applicable) database. Why not incorporate testcontainers or similar tool to mimic real-life situation then it'll run actual migrations from source code and you'll test what's necessary.
The disabling of db sounds dodgy unless you are testing integrity of your application disregarding the database. In that case, provide FlywayMigrationStrategy as null - it will stop from executing any migrations and you'll have no DB environment.
Once again - it's up to your application
I have a question about the usage of DataJpaTest annotation. I am trying to test a Jpa repository, exactly as shown in the documentation.
I am getting an error that the HttpServletRequest cannot be resolved. It is because a different bean of mine is using it.
Why is the test trying to use the irrelevant bean? I would expect a DataJpaTest to only load Jpa related beans, repositories, etc. It seems it is trying to load all beans, which of course have their own dependencies.
What is the correct way to write a DataJpaTest so that I only focus on my Jpa repositories?
This is on Java 8, Spring Boot 2 and junit 5.
Update 1: thanks for the comments guys. My test class is literally based on the documentation.
My Spring Boot application class is like this:
#SpringBootApplication
#EnableSwagger2
#EnableCorsFilter
#ComponentScan(basePackages = {"com.acme.superapp"})
#SuppressWarnings("HideUtilityClassConstructor")
public class Swagger2SpringBoot {
public static void main(String[] args) {
new SpringApplication(Swagger2SpringBoot.class).run(args);
}
}
This actually helped because after I removed the "ComponentScan" annotation I get a different error, related to Swagger.
So it seems these annotations are affecting my test.
Thanks for your comments guys. The comment from M. Deinum and shinjw showed me to the correct path. I needed to slim down the entrypoint so that it does not have anything extra.
According to the documentation :
If you structure your code in a sensible way, your
#SpringBootApplication class is used by default as the configuration
of your tests.
It then becomes important not to litter the application’s main class
with configuration settings that are specific to a particular area of
its functionality.
Therefore I moved the ComponentScan and EnableSwagger2 annotations elsewhere, in different Configuration classes and this did the trick.
Thanks for your help!
To test a component/bean in a Spring Boot application, the testing part of the Spring Boot documentation provides much information and multiple ways :
#Test, #SpringBootTest, #WebMvcTest, #DataJpaTest and still many other ways.
Why provide so many ways ?
How decide the way to favor ?
Should I consider as integration tests my test classes annotated with Spring Boot test annotations such as #SpringBootTest, #WebMvcTest, #DataJpaTest ?
PS : I created this question because I noticed that many developers (even experienced) don't get the consequences to use an annotation rather than another.
TL-DR
write plain unit tests for components that you can straightly test without loading a Spring container (run them in local and in CI build).
write partial integration tests/slicing unit test for components that you cannot straightly test without loading a Spring container such as components related to JPA, controllers, REST clients, JDBC ... (run them in local and in CI build)
write some full integration tests (end-to-end tests) for some high-level components where it brings values (run them in CI build).
3 main ways to test a component
plain unit test (doesn't load a Spring container)
full integration test (load a Spring container with all configuration and beans)
partial integration test/ test slicing (load a Spring container with very restricted configurations and beans)
Can all components be tested in these 3 ways ?
In a general way with Spring any component can be tested in integration tests and only some kinds of components are suitable to be tested unitary(without container).
But note that with or without spring, unitary and integration tests are not opposed but complementary.
How to determine if a component can be plain tested (without spring) or only tested with Spring?
You recognize a code to test that doesn't have any dependencies from a Spring container as the component/method doesn't use Spring feature to perform its logical.
Take that FooService class :
#Service
public class FooService{
private FooRepository fooRepository;
public FooService(FooRepository fooRepository){
this.fooRepository = fooRepository;
}
public long compute(...){
List<Foo> foos = fooRepository.findAll(...);
// core logic
long result =
foos.stream()
.map(Foo::getValue)
.filter(v->...)
.count();
return result;
}
}
FooService performs some computations and logic that don't need Spring to be executed.
Indeed with or without container the compute() method contains the core logic we want to assert.
Reversely you will have difficulties to test FooRepository without Spring as Spring Boot configures for you the datasource, the JPA context, and instrument your FooRepository interface to provide to it a default implementation and multiple other things.
Same thing for testing a controller (rest or MVC).
How could a controller be bound to an endpoint without Spring? How could the controller parse the HTTP request and generate an HTTP response without Spring? It simply cannot be done.
1)Writing a plain unit test
Using Spring Boot in your application doesn't mean that you need to load the Spring container for any test class you run.
As you write a test that doesn't need any dependencies from the Spring container, you don't have to use/load Spring in the test class.
Instead of using Spring you will instantiate yourself the class to test and if needed use a mock library to isolate the instance under test from its dependencies.
That is the way to follow because it is fast and favors the isolation of the tested component.
Here how to unit-test the FooService class presented above.
You just need to mock FooRepository to be able to test the logic of FooService.
With JUnit 5 and Mockito the test class could look like :
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
#ExtendWith(MockitoExtension.class)
class FooServiceTest{
FooService fooService;
#Mock
FooRepository fooRepository;
#BeforeEach
void init{
fooService = new FooService(fooRepository);
}
#Test
void compute(){
List<Foo> fooData = ...;
Mockito.when(fooRepository.findAll(...))
.thenReturn(fooData);
long actualResult = fooService.compute(...);
long expectedResult = ...;
Assertions.assertEquals(expectedResult, actualResult);
}
}
2)Writing a full integration test
Writing an end-to-end test requires to load a container with the whole configuration and beans of the application.
To achieve that #SpringBootTest is the way :
The annotation works by creating the ApplicationContext used in your
tests through SpringApplication
You can use it in this way to test it without any mock :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
#SpringBootTest
public class FooTest {
#Autowired
Foo foo;
#Test
public void doThat(){
FooBar fooBar = foo.doThat(...);
// assertion...
}
}
But you can also mock some beans of the container if it makes sense :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
#SpringBootTest
public class FooTest {
#Autowired
Foo foo;
#MockBean
private Bar barDep;
#Test
public void doThat(){
Mockito.when(barDep.doThis()).thenReturn(...);
FooBar fooBar = foo.doThat(...);
// assertion...
}
}
Note the difference for mocking as you want to mock a plain instance of a Bar class (org.mockito.Mock annotation)and that you want to mock a Bar bean of the Spring context (org.springframework.boot.test.mock.mockito.MockBean annotation).
Full integration tests have to be executed by the CI builds
Loading a full spring context takes time. So you should be cautious with #SpringBootTest as this may make unit tests execution to be very long and generally you don't want to strongly slow down the local build on the developer's machine and the test feedback that matters to make the test writing pleasant and efficient for developers.
That's why "slow" tests are generally not executed on the developer's machines.
So you should make them integration tests (IT suffix instead of Test suffix in the naming of the test class) and make sure that these are executed only in the continuous integration builds.
But as Spring Boot acts on many things in your application (rest controllers, MVC controllers, JSON serialization/deserialization, persistence, and so for...) you could write many unit tests that are only executed on the CI builds and that is not fine either.
Having end-to-end tests executed only on the CI builds is ok but having also persistence, controllers or JSON tests executed only on the CI builds is not ok at all.
Indeed, the developer build will be fast but as drawback the tests execution in local will detect only a small part of the possible regressions...
To prevent this caveat, Spring Boot provides an intermediary way : partial integration test or the slice testing (as they call it) : the next point.
3)Writing a partial integration test focusing on a specific layer or concern thanks to slice testing
As explained in the point "Recognizing a test that can be plain tested (without spring))", some components can be tested only with a running container.
But why using #SpringBootTest that loads all beans and configurations of your application while you would need to load only a few specific configuration classes and beans to test these components?
For example why loading a full Spring JPA context (beans, configurations, in memory database, and so forth) to test the controller part?
And reversely why loading all configurations and beans associated to Spring controllers to test the JPA repository part?
Spring Boot addresses this point with the slice testing feature.
These are not as much as fast than plain unit tests (that is without container) but these are really much faster than loading a whole spring context.
So executing them on the local machine is generally very acceptable.
Each slice testing flavor loads a very restricted set of auto-configuration classes that you can modify if needed according to your requirements.
Some common slice testing features :
Auto-configured JSON Tests : #JsonTest
To test that object JSON serialization and deserialization is working
as expected, you can use the #JsonTest annotation.
Auto-configured Spring MVC Tests : #WebMvcTest
To test whether Spring MVC controllers are working as expected, use
the #WebMvcTest annotation.
Auto-configured Spring WebFlux Tests : #WebFluxTest
To test that Spring WebFlux controllers are working as expected, you
can use the #WebFluxTest annotation.
Auto-configured Data JPA Tests : #DataJpaTest
You can use the #DataJpaTest annotation to test JPA applications.
And you have still many other slice flavors that Spring Boot provides to you.
See the testing part of the documentation to get more details.
Note that if you need to define a specific set of beans to load that the built-in test slice annotations don't address, you can also create your own test slice annotation(https://spring.io/blog/2016/08/30/custom-test-slice-with-spring-boot-1-4).
4)Writing a partial integration test focusing on specific beans thanks to lazy bean initialization
Some days ago, I have encountered a case where I would test in partial integration a service bean that depends on several beans that themselves also depend on other beans.
My problem was that two deep dependency beans have to be mocked for usual reasons (http requests and a query with large data in database).
Loading all the Spring Boot context looked an overhead, so I tried to load only specific beans.
To achieve that, I annotation the test class with #SpringBootTest and I specified the classes attribute to define the configuration/beans classes to load.
After many tries I have gotten something that seemed working but I had to define an important list of beans/configurations to include.
That was really not neat nor maintainable.
So as clearer alternative, I chose to use the lazy bean initialization feature provided by Spring Boot 2.2 :
#SpringBootTest(properties="spring.main.lazy-initialization=true")
public class MyServiceTest { ...}
That has the advantage to load only beans used at runtime.
I don't think at all that using that property has to be the norm in test classes but in some specific test cases, that appears the right way.
I have a spring mvc application, and I am using jdbctemplate for my database Dao objects.
How should I go about integration testing?
Where should I put my integration testing files, is this layout correct:
/src/main/test/integration/...
or
/src/main/integration/...
How will this work, for my test cases, I should have a base class or some code in my setup that will run once before starting my testing where it loads the spring configuration/application context.
How should I do this?
If You are using Maven, tests should go to src/test/java. If You're not, it seems like a reasonable place anyway.
To set up spring context You should use #RunWith(SpringJUnit4ClassRunner.class) together with #ContextConfiguration, no initialization code necessary.