With the @SpringBootTest
annotation, Spring Boot provides a convenient way to start up
an application context to be used in a test. In this tutorial, we’ll discuss when to use
@SpringBootTest
and when to better use other tools for testing. We’ll also look into
different ways to customize the application context and how to reduce test runtime.
Example Code
This article is accompanied by a working code example on GitHub.The “Testing with Spring Boot” Series
This tutorial is part of a series:
- Unit Testing with Spring Boot
- Testing Spring MVC Web Controllers with Spring Boot and
@WebMvcTest
- Testing JPA Queries with Spring Boot and
@DataJpaTest
- Testing with Spring Boot and
@SpringBootTest
If you like learning from videos, make sure to check out Philip’s Testing Spring Boot Applications Masterclass (if you buy through this link, I get a cut).
Integration Tests vs. Unit Tests
Before we start into integration tests with Spring Boot, let’s define what sets an integration test apart from a unit test.
A unit test covers a single “unit”, where a unit commonly is a single class, but can also be a cluster of cohesive classes that is tested in combination.
An integration test can be any of the following:
- a test that covers multiple “units”. It tests the interaction between two or more clusters of cohesive classes.
- a test that covers multiple layers. This is actually a specialization of the first case and might cover the interaction between a business service and the persistence layer, for instance.
- a test that covers the whole path through the application. In these tests, we send a request to the application and check that it responds correctly and has changed the database state according to our expectations.
Spring Boot provides the @SpringBootTest
annotation which we can use to
create an application context containing all the objects we need for all of the
above test types. Note, however, that overusing @SpringBootTest
might lead to very long-running test suites.
So, for simple tests that cover multiple units we should rather create plain tests, very similar to unit tests, in which we manually create the object graph needed for the test and mock away the rest. This way, Spring doesn’t fire up a whole application context each time the test is started.
Test Slices
We can test our Spring Boot application as a whole, unit by unit, and also layer by layer. Using Spring Boot’s test slice annotations we can test each layer separately.
Before we look into the @SpringBootTest
annotation in detail, let’s explore the test slice annotation to check if @SpringBootTest
is really what you want.
The @SpringBootTest
annotation loads the complete Spring application context. In contrast, a test slice annotation only loads beans required to test a particular layer. And because of this, we can avoid unnecessary mocking and side effects.
@WebMvcTest
Our web controllers bear many responsibilities, such as listening to the HTTP request, validating the input, calling the business logic, serializing the output, and translating the Exceptions to a proper response. We should write tests to verify all these functionalities.
The @WebMvcTest
test slice annotation will set up our application context with just enough components and configurations required to test our web controller layer. For instance, it will set up our @Controller
’s, @ControllerAdvice
’s, a MockMvc
bean, and some other auto configuration.
To read more on @WebMvcTest
and to find out how we can verify each of those responsibilities, read my article on Testing MVC Web Controllers with Spring Boot and @WebMvcTest.
@WebFluxTest
@WebFluxTest
is used when we want to test our WebFlux controllers. @WebFluxTest
works similar to the @WebMvcTest
annotation difference here is that instead of the
Web MVC components and configurations, it spins up the WebFlux ones. One such bean is the WebTestClient
, using which we can test our WebFlux endpoints.
@DataJpaTest
Just like @WebMvcTest
allows us to test our web layer, @DataJpaTest
is used to test the persistence layer.
It configures our entities, repositories and also sets up an embedded database. Now, this is all good but,
what does testing our persistence layer mean? What exactly are we testing? If queries, then what kind of queries? To find out answers for all these questions
and more, read my article on Testing JPA Queries with Spring Boot and @DataJpaTest
.
@DataJdbcTest
Spring Data JDBC is another member of the Spring Data family. If we are using this project
and want to test the persistence layer then we can make use of the @DataJdbcTest
annotation.
@DataJdbcTest
automatically configures an embedded test database and JDBC repositories defined in our project for us.
Another similar project is Spring JDBC which gives us the JdbcTemplate
object to perform direct queries. The @JdbcTest
annotation
autoconfigures the DataSource
object that is required to test our JDBC queries.
Dependencies
The code examples in this article only need the dependencies to Spring Boot’s test starter and to JUnit Jupiter:
dependencies {
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.junit.jupiter:junit-jupiter:5.4.0')
}
Creating an ApplicationContext with @SpringBootTest
@SpringBootTest
by default starts searching in the current package of the
test class and then searches upwards through the package structure, looking for
a class annotated with @SpringBootConfiguration
from which it then reads the
configuration to create an application context. This class is usually our main
application class since the @SpringBootApplication
annotation includes
the @SpringBootConfiguration
annotation. It then creates an
application context very similar to the one that would be started in a production
environment.
We can customize this application context in many different ways, as described in the next section.
Because we have a full application context, including web controllers, Spring
Data repositories, and data sources, @SpringBootTest
is very convenient for
integration tests that go through all layers of the application:
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class RegisterUseCaseIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@Test
void registrationWorksThroughAllLayers() throws Exception {
UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net");
mockMvc.perform(post("/forums/{forumId}/register", 42L)
.contentType("application/json")
.param("sendWelcomeMail", "true")
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isOk());
UserEntity userEntity = userRepository.findByName("Zaphod");
assertThat(userEntity.getEmail()).isEqualTo("zaphod@galaxy.net");
}
}
@ExtendWith
The code examples in this tutorial use the @ExtendWith
annotation to tell
JUnit 5 to enable Spring support. As of Spring Boot 2.1, we no longer need to
load the SpringExtension
because it's included as a meta annotation in the
Spring Boot test annotations like @DataJpaTest
, @WebMvcTest
, and
@SpringBootTest
.
Here, we additionally use @AutoConfigureMockMvc
to add a MockMvc
instance
to the application context.
We use this MockMvc
object to perform a POST
request to our application
and to verify that it responds as expected.
We then use the UserRepository
from the application context to verify that the
request has lead to an expected change in the state of the database.
Customizing the Application Context
We can turn a lot of knobs to customize the application context
created by @SpringBootTest
. Let’s see which options we have.
Caution when Customizing the Application Context
Each customization of the application context is one more thing that makes it different from the "real" application context that is started up in a production setting. So, in order to make our tests as close to production as we can, we should only customize what's really necessary to get the tests running!
Adding Auto-Configurations
Above, we’ve already seen an auto-configuration in action:
@SpringBootTest
@AutoConfigureMockMvc
class RegisterUseCaseIntegrationTest {
...
}
There are lot of other auto-configurations available that each add other beans to the application context. Here are some other useful ones from the documentation:
@AutoConfigureWebTestClient
: AddsWebTestClient
to the test application context. It allows us to test server endpoints.@AutoConfigureTestDatabase
: This allows us to run the test against a real database instead of the embedded one.@RestClientTest
: It comes in handy when we want to test ourRestTemplate
s. It autoconfigures the required components plus aMockRestServiceServer
object which helps us mock responses for the requests coming from theRestTemplate
calls.@JsonTest
: Autoconfigures JSON mappers and classes such asJacksonTester
orGsonTester
. Using these we can verify whether our JSON serialization/deserialization is working properly or not.
Setting Custom Configuration Properties
Often, in tests it’s necessary to set some configuration properties to a value that’s different from the value in a production setting:
@SpringBootTest(properties = "foo=bar")
class SpringBootPropertiesTest {
@Value("${foo}")
String foo;
@Test
void test(){
assertThat(foo).isEqualTo("bar");
}
}
If the property foo
exists in the default setting, it will be overridden
by the value bar
for this test.
Externalizing Properties with @ActiveProfiles
If many of our tests need the same set of properties, we can create a
configuration file application-<profile>.properties
or application-<profile>.yml
and load the properties from that file by activating a certain profile:
# application-test.yml
foo: bar
@SpringBootTest
@ActiveProfiles("test")
class SpringBootProfileTest {
@Value("${foo}")
String foo;
@Test
void test(){
assertThat(foo).isEqualTo("bar");
}
}
Setting Custom Properties with @TestPropertySource
Another way to customize a whole set of properties is with the @TestPropertySource
annotation:
# src/test/resources/foo.properties
foo=bar
@SpringBootTest
@TestPropertySource(locations = "/foo.properties")
class SpringBootPropertySourceTest {
@Value("${foo}")
String foo;
@Test
void test(){
assertThat(foo).isEqualTo("bar");
}
}
All properties from the foo.properties
file are loaded into the application
context. @TestPropertySource
also to configure a lot more.
Injecting Mocks with @MockBean
If we only want to test a certain part of the application instead of the
whole path from incoming request to database, we can replace certain
beans in the application context by using @MockBean
:
@SpringBootTest
class MockBeanTest {
@MockBean
private UserRepository userRepository;
@Autowired
private RegisterUseCase registerUseCase;
@Test
void testRegister(){
// given
User user = new User("Zaphod", "zaphod@galaxy.net");
boolean sendWelcomeMail = true;
given(userRepository.save(any(UserEntity.class))).willReturn(userEntity(1L));
// when
Long userId = registerUseCase.registerUser(user, sendWelcomeMail);
// then
assertThat(userId).isEqualTo(1L);
}
}
In this case, we have replaced the UserRepository
bean with a mock.
Using Mockito’s given
method, we have specified the expected behavior for this
mock in order to test a class that uses this repository.
You can read more about the @MockBean
annotation in my article about mocking.
Adding Beans with @Import
If certain beans are not included in the default application context, but we need
them in a test, we can import them using the @Import
annotation:
package other.namespace;
@Component
public class Foo {
}
@SpringBootTest
@Import(other.namespace.Foo.class)
class SpringBootImportTest {
@Autowired
Foo foo;
@Test
void test() {
assertThat(foo).isNotNull();
}
}
By default, a Spring Boot application includes all components it finds within its package and sub-packages, so this will usually only be needed if we want to include beans from other packages.
Overriding Beans with @TestConfiguration
With @TestConfiguration
we can not only include additional beans required for tests but also override the
beans already defined in the application. Read more about it in our article on Testing with @TestConfiguration
Creating a Custom @SpringBootApplication
We can even create a whole custom Spring Boot application to start up in tests.
If this application class is in the same package as the real application class,
but in the test sources rather than
the production sources, @SpringBootTest
will find it before the actual application class
and load the application context from this application instead.
Alternatively, we can tell Spring Boot which application class to use to create an application context:
@SpringBootTest(classes = CustomApplication.class)
class CustomApplicationTest {
}
When doing this, however, we’re testing an application context that may be completely different from the production environment, so this should be a last resort only when the production application cannot be started in a test environment. Usually, there are better ways, though, such as to make the real application context configurable to exclude beans that won’t start in a test environment. Let’s look at this in an example.
Let’s say we use the @EnableScheduling
annotation on our application class.
Each time the application context is started (even in tests), all @Scheduled
jobs will
be started and may conflict with our tests. We usually don’t want the jobs to run in tests,
so we can create a second application class without the @EnabledScheduling
annotation
and use this in the tests. However, the better solution would be to create a configuration class
that can be toggled with a property:
@Configuration
@EnableScheduling
@ConditionalOnProperty(
name = "io.reflectoring.scheduling.enabled",
havingValue = "true",
matchIfMissing = true)
public class SchedulingConfiguration {
}
We have moved the @EnableScheduling
annotation from our application class to this special
confgiuration class. Setting the property io.reflectoring.scheduling.enabled
to false
will cause this class not to be loaded as part of the application context:
@SpringBootTest(properties = "io.reflectoring.scheduling.enabled=false")
class SchedulingTest {
@Autowired(required = false)
private SchedulingConfiguration schedulingConfiguration;
@Test
void test() {
assertThat(schedulingConfiguration).isNull();
}
}
We have now successfully deactivated the scheduled jobs
in the tests. The property io.reflectoring.scheduling.enabled
can be specified in any of the ways described
above.
Why are my Integration Tests so slow?
A code base with a lot of @SpringBootTest
-annotated tests may take quite some time
to run. The Spring test support is smart enough
to only create an application context once and re-use it in following tests, but if different tests need different application
contexts, it will still create a separate context for each test, which takes some time for
each test.
All of the customizing options described above will cause Spring to create a new application context. So, we might want to create one single configuration and use it for all tests so that the application context can be re-used.
If you’re interested in the time your tests spend for setup and Spring application contexts, you may want to have a look at JUnit Insights, which can be included in a Gradle or Maven build to produce a nice report about how your JUnit 5 tests spend their time.
Conclusion
@SpringBootTest
is a very convenient method to set up an application context for tests
that is very close the one we’ll have in production. There are a lot of options to customize
this application context, but they should be used with care since we want our tests to run
as close to production as possible.
@SpringBootTest
brings the most value if we want to test the whole way through the application.
For testing only certain slices or layers of the application, we have other options available.
The example code used in this article is available on github.
If you like learning from videos, make sure to check out Philip’s Testing Spring Boot Applications Masterclass (if you buy through this link, I get a cut).