[Note] This article is translated from: Testing with Spring Boot and @SpringBootTest-Reflectoring
Using the @SpringBootTest
annotation, Spring Boot provides a convenient way to start the application context to be used in the test. In this tutorial, we will discuss when to use @SpringBootTest
and when it is better to use other tools for testing. We will also look at different ways to customize the application context and how to reduce test run time.
Code example
This article attached on GitHub working code examples.
"Testing with Spring Boot" series
This tutorial is part of a series:
- Use Spring Boot for unit testing
- Use Spring Boot and @WebMvcTest to test MVC Web Controller
- Use Spring Boot and @DataJpaTest to test JPA query
- Use Spring Boot and @SpringBootTest for testing
Integration test and unit test
Before starting to use Spring Boot for integration testing, let us define the difference between integration testing and unit testing.
Unit testing covers a single "unit", where a unit is usually a single class, but it can also be a cluster within a group of combined tests.
The integration test can be any of the following:
- Covers multiple "units" of testing. It tests the interaction between two or more intra-cluster clusters.
- Test covering multiple layers. This is actually a specialization of the first case, for example, it may cover the interaction between the business service and the persistence layer.
- Tests covering the entire application path. In these tests, we sent a request to the application and checked whether it responded correctly and changed the database state according to our expectations.
Spring Boot provides the @SpringBootTest
annotation, we can use it to create an application context, which contains all the objects we need for all the above test types. But please note that excessive use of @SpringBootTest
may cause the test suite to run very long .
Therefore, for a simple test covering multiple units, we should create a simple test, which unit test . In the unit test, we manually create the object graph required for the test and simulate the rest. In this way, Spring will not start the entire application context at the beginning of each test.
Test slice
We can test our Spring Boot application as a whole, unit by unit, or layer by layer. Using Spring Boot's test slice annotation , we can test each layer separately.
Before we study the @SpringBootTest
annotations in detail, let's explore the test slice annotations to check @SpringBootTest
is really what you want.@SpringBootTest
annotation loads the complete Spring application context. In contrast, test slice annotations only load the beans needed to test a specific layer. Because of this, we can avoid unnecessary simulations and side effects.
@WebMvcTest
Our web controller takes on many responsibilities, such as listening for HTTP requests, validating input, invoking business logic, serializing output, and converting exceptions into correct responses. We should write tests to verify all these functions.@WebMvcTest
test slice annotation will use just enough components and configurations to set up our application context to test our web controller layer. For example, it will set our @Controller
, @ControllerAdvice
, a MockMvc
bean and some other automatically configure .
To read more about @WebMvcTest more information and to learn how we verify each role, please read my About article uses Spring Boot and @WebMvcTest test MVC Web controller .
@WebFluxTest
@WebFluxTest
used to test the WebFlux controller. @WebFluxTest
works similarly to the @WebMvcTest
annotation, the difference is that it is not a Web MVC component and configuration, but a WebFlux component and configuration. One of the beans is WebTestClient, which we can use to test our WebFlux endpoint.
@DataJpaTest
Just like @WebMvcTest
allows us to test our web layer, @DataJpaTest
used to test the persistence layer.
It configures our entities, repositories and sets up embedded databases. Now, all this is fine, but what does it mean to test our persistence layer? What are we testing? If query, then what kind of query? To find out the answers to all these questions, read my article on the use of Spring Boot and @DataJpaTest test JPA query .
@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 use the @DataJdbcTest
annotation. @DataJdbcTest
will automatically configure the embedded test database and JDBC repository defined in our project for us.
Another similar project is Spring JDBC, which provides us with JdbcTemplate
objects to perform direct queries. @JdbcTest
DataSource
object needed to test our JDBC query. rely
The code examples in this article only need to rely on Spring Boot's test starter and JUnit Jupiter:
dependencies {
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.junit.jupiter:junit-jupiter:5.4.0')
}
Use @SpringBootTest to create ApplicationContext
@SpringBootTest
starts to search in the current package of the test class by default, and then searches upward in the package structure, looking for @SpringBootConfiguration
, and then reads the configuration from it to create the application context. This class is usually our main application class, because the @SpringBootApplication
annotation includes the @SpringBootConfiguration
annotation. Then, it creates an application context that is very similar to the application context launched in the production environment.
We can customize this application context in many different ways, as described in the next section.
Because we have a complete application context, including web controllers, Spring data repositories, and data sources, @SpringBootTest
is very convenient for integration testing throughout 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. Starting from Spring Boot 2.1 , we no longer need to load SpringExtension because it is included as a meta-annotation in Spring Boot test annotations, such as@DataJpaTest
,@WebMvcTest
and@SpringBootTest
.
Here, we additionally use @AutoConfigureMockMvc
add the MockMvc instance to the application context.
We use this MockMvc
object to perform a POST request to our application and verify that it responds as expected.
Then, we use UserRepository
in the application context to verify whether the request caused the expected change in the database state.
Custom application context
There are many ways to customize the application context created by @SpringBootTest
Let us see what options we have.
Precautions when customizing the application context
Each customization of the application context is another thing that makes it different from the "real" application context launched in a production setting. Therefore, in order to make our tests as close to production as possible, we should only customize what is really needed to make the tests run!
Add automatic configuration
In the above, we have seen the role of automatic configuration:
@SpringBootTest
@AutoConfigureMockMvc
class RegisterUseCaseIntegrationTest {
...
}
There are many other auto-configuration , each of which can add other beans to the application context. Here are some other useful contents in the document:
@AutoConfigureWebTestClient
: AddWebTestClient
to the test application context. It allows us to test server endpoints.@AutoConfigureTestDatabase
: This allows us to run tests against real databases instead of embedded databases.@RestClientTest
: It will come in handy when we want to test ourRestTemplate
It automatically configures the required components and aMockRestServiceServer
object, which helps us simulate the response to the requestRestTemplate
@JsonTest
: Automatically configure JSON mappers and classes, such asJacksonTester
orGsonTester
. Using these we can verify whether our JSON serialization/deserialization is working properly.
Set custom configuration properties
Usually, some configuration properties need to be set to different values from the values in the production settings during testing:
@SpringBootTest(properties = "foo=bar")
class SpringBootPropertiesTest {
@Value("${foo}")
String foo;
@Test
void test(){
assertThat(foo).isEqualTo("bar");
}
}
If the attribute foo
present in the default setting, it will be overwritten bar
Use @ActiveProfiles to externalize properties
If many of our tests require the same set of attributes, we can create a configuration file application-<profile>.propertie
or application-<profile>.yml
and load the attributes from that file by activating a certain configuration file:
# application-test.yml
foo: bar
@SpringBootTest
@ActiveProfiles("test")
class SpringBootProfileTest {
@Value("${foo}")
String foo;
@Test
void test(){
assertThat(foo).isEqualTo("bar");
}
}
Use @TestPropertySource to set custom properties
Another way to customize the entire set of attributes is to use 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");
}
}
foo.properties
file are loaded into the application context. @TestPropertySource
can also be configured more.
Use @MockBean to inject mocks
If we only want to test a certain part of the application instead of the entire path from the incoming request to the database, we can use @MockBean
replace some beans in the application context:
@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 replaced UserRepository bean
with analog. Use Mockito
of given
method, we specify the expected behavior of this simulation to test the use of this repository class.
You can me about the simulation article read about in @MockBean
more information annotations.
Use @Import to add Bean
If some beans are not included in the default application context, but we need them in the test, we can import them @Import
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 the components it finds in its packages and subpackages, so we usually only need to do this if we want to include beans in other packages.
Use @TestConfiguration to overwrite Bean
Using @TestConfiguration
, we can not only include other beans needed for testing, but also overwrite beans that have been defined in the application. Read more in our article about 16184d680b01fb using @TestConfiguration
to test
Create custom @SpringBootApplication
We can even create a complete custom Spring Boot application to start the test. If this application class is in the same package as the real application class, but in the test source instead of the production source, @SpringBootTest
will find it before the actual application class and load the application context from this application.
Alternatively, we can tell Spring Boot which application class to use to create the application context:
@SpringBootTest(classes = CustomApplication.class)
class CustomApplicationTest {
}
However, when doing this, that may be completely different from the production environment, so this should be the last resort only when the production application cannot be launched in the test environment. However, there are usually better ways, such as making the real application context configurable to exclude beans that will not be launched in the test environment. Let us look at an example.
Suppose we use the @EnableScheduling
annotation on the application class. Every time the application context is launched (even during testing), all @Scheduled
jobs will be launched and may conflict with our testing. We usually don't want the job to run in the test, so we can create a second @EnabledScheduling
annotation and use it in the test. However, a better solution is to create a configuration class that can use attribute switching:
@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 configuration 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 disabled scheduled assignments in the test. The attribute io.reflectoring.scheduling.enabled
can be specified in any of the above-mentioned ways.
Why is my integration test so slow?
A code base containing a large number of @SpringBootTest
annotation tests may take a considerable amount of time to run. Spring's test support is smart enough to to create an application context once and reuse it in subsequent tests, but if different tests require different application contexts, it will still create a separate context for each test, which takes some time To complete each test.
All the customization options described above will cause Spring to create a new application context . Therefore, we may want to create a configuration and use it for all tests so that the application context can be reused.
If you are interested in the time the test spends on setup and Spring application context, you may want to check out JUnit Insights , which can be included in a Gradle or Maven build to generate a good report on how JUnit 5 spends time .
in conclusion
@SpringBootTest
is a very convenient way to set up the application context for testing, it is very close to the context we will use in production. There are many options to customize this application context, but they should be used with caution because we want our tests to run as close to production as possible.
If we want to test in the entire application, @SpringBootTest
will bring the most value. In order to test only certain slices or layers of the application, we have other options available.
The sample code used in this article can be found on github at .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。