“This is the first day of my participation in the Gwen Challenge in November. See details of the event: The last Gwen Challenge in 2021”.

参 考 答 案 : Testing with SpringBoot and @springboot-reflecmotoring

Using the @SpringBoottest annotation, SpringBoot provides a convenient way to launch the application context you want to use in your tests. In this tutorial, we will discuss when to use @SpringbooTtest and when it is better to use other tools for testing. We’ll also look at different ways to customize application contexts and how to reduce test run time.

 code example

Examples of working code on GitHub are attached to this article.

“Test with Spring Boot” series

This tutorial is part of a series:

  1. Use Spring Boot for unit testing
  2. Test the MVC Web Controller using Spring Boot and @webMvcTest
  3. Test JPA queries using Spring Boot and @datajPatest
  4. Use SpringBoot and @SpringBoottest to test

Integration testing and unit testing

Before we start integration testing with Spring Boot, let’s define the difference between integration testing and unit testing. Unit tests cover a single “unit,” one of which is usually a single class, but can also be a set of clusters that combine tests. Integration tests can be any of the following:

  • Tests that cover multiple “units.” It tests interactions between two or more clustered clusters.
  • Tests that cover multiple layers. This is actually a specialization of the first case and might cover, for example, interactions between business services and the persistence layer.
  • Tests that cover the entire application path. In these tests, we send requests to the application, check that it responds correctly and change the database state as we expect.

SpringBoot provides the @SpringBooTtest annotation that we can use to create an application context that contains all the objects we need for all of the above test types. Note, however, that overuse of @Springboottest can result in test suites that take a very long time to run. Therefore, for simple tests that cover multiple units, we should create simple tests, much like unit tests, in which we manually create the object graph needed for the test and simulate the rest. This way, Spring does not launch the entire application context at the start of each test.

The test section

We can test our Spring Boot application as a whole, unit by unit, or layer by layer. Using Spring Boot’s test slice annotations, we can test each layer individually. Before we dive into @SpringbooTtest annotations in detail, let’s explore test slice annotations to check if @SpringbooTtest is really what you want. The @Springboottest annotation loads the full Spring application context. In contrast, test slice annotations load only beans needed to test a particular 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 turning exceptions into correct responses. We should write tests to verify all of these capabilities. The @webMvctest test slice annotation will set up our application context with just enough components and configurations to test our Web controller layer. For example, it will set up our @Controller, @ControllerAdvice, a MockMvc bean, and some other automatic configurations. To read more about @WebMvcTest and how we validate each responsibility, read my article on testing MVC Web Controllers with Spring Boot and @WebMvcTest.

@WebFluxTest

@webFluxtest Tests the WebFlux controller. @WebFluxTest works like the @WebMVctest annotation except that instead of a Web MVC component and configuration, it starts a WebFlux component and configuration. One of these beans is WebTestClient, which we can use to test our WebFlux endpoint.

@DataJpaTest

Just as @webMvcTest allows us to test our Web layer, @DatajPatest is used to test the persistence layer. It configures our entities, repositories, and sets up the embedded database. Now, that’s all well and good, but what does testing our persistence layer mean? What exactly are we testing? If so, what kind of query? To find out the answers to all of these questions, 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, we can use the @DatajDBcTest annotation. @datajDBctest will automatically configure for us the embedded test database and JDBC repository defined in our project. Another similar project is Spring JDBC, which provides us with a JdbcTemplate object to perform direct queries. The @jdbcTest annotation automatically configures the DataSource object needed to test our JDBC query. The code examples in this article rely only 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')}Copy the code

Create the ApplicationContext using @Springboottest

By default, @SpringBooTtest starts searching in the current package of the test class, then searches up in the package structure for the class annotated with @SpringBootConfiguration, and 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. It then creates an application context that is very similar to the one 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 the Web controller, Spring data repository, and data source, @Springboottest is handy for integration testing across 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(a) throws Exception {
    UserResource user = new UserResource("Zaphod"."[email protected]");

    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("[email protected]"); }}Copy the code

@ExtendWith

The code examples in this tutorial use the @extendWith annotation to tell JUnit 5 to enable Spring support. Starting with Spring Boot 2.1, we no longer need to load SpringExtension because it is included as a meta-annotation in the Spring Boot test annotations, Examples are @datajpatest, @webMvctest, and @Springboottest.

Here, we also add the MockMvc instance to the application context using @AutoConfiguRemockMVC. We use this MockMvc object to execute the POST request to our application and verify that it responds as expected. We then use UserRepository in the application context to verify that the request causes the expected change in the database state.

Customize the application context

There are many ways we can customize the application context created by @Springboottest. Let’s see what our options are.

Precautions for customizing the application context

Each customization of an application context is another thing that sets it apart from the “real” application context launched in a production setting. Therefore, in order to get our tests as close to production as possible, we should only customize what we really need to get the tests running!

Adding Automatic Configuration

Above, we have seen the role of autoconfiguration:

@SpringBootTest
@AutoConfigureMockMvc
class RegisterUseCaseIntegrationTest {... }Copy the code

There are many other automatic configurations available, each of which adds additional beans to the application context. Here are some other useful things from the document:

  • @AutoConfigureWebTestClientWill:WebTestClientAdd to the test application context. It allows us to test the server endpoint.
  • @AutoConfigureTestDatabase: This allows us to run tests against a real database rather than an embedded database.
  • @RestClientTest: When we want to test ourRestTemplateIt will come in handy. It automatically configures the required components as well as oneMockRestServiceServerObject that helps us simulate data fromRestTemplateThe response to the call’s request.
  • @JsonTest: Automatically configures JSON mapper and classes, for exampleJacksonTesterGsonTester. Using this we can verify that our JSON serialization/deserialization works.

Set custom configuration properties

Typically, some configuration properties need to be set to values different from those in production Settings during testing:

@SpringBootTest(properties = "foo=bar")
class SpringBootPropertiesTest {

  @Value("${foo}")
  String foo;

  @Test
  void test(a){
    assertThat(foo).isEqualTo("bar"); }}Copy the code

If the attribute foo exists in the default setting, it will be overridden by the value bar for this test.

Externalize properties using @ActiveProfiles

If many of our tests require the same set of properties, we can create a profile application- .propertie or application- .yml and load properties from that file by activating a profile:

# application-test.yml
foo: bar
Copy the code
@SpringBootTest
@ActiveProfiles("test")
class SpringBootProfileTest {

  @Value("${foo}")
  String foo;

  @Test
  void test(a){
    assertThat(foo).isEqualTo("bar"); }}Copy the code

Set custom properties using @testpropertysource

Another way to customize the entire property set is to use the @testpropertysource annotation:

# src/test/resources/foo.properties
foo=bar
Copy the code
@SpringBootTest
@TestPropertySource(locations = "/foo.properties")
class SpringBootPropertySourceTest {

  @Value("${foo}")
  String foo;

  @Test
  void test(a){
    assertThat(foo).isEqualTo("bar"); }}Copy the code

All properties in the foo.properties file are loaded into the application context. @testpropertysource can be configured with more.

Inject the emulation using @MockBean

If we want to test only one part of the application and not the entire path from the incoming request to the database, we can replace some beans in the application context with @MockBean:

@SpringBootTest
class MockBeanTest {

  @MockBean
  private UserRepository userRepository;

  @Autowired
  private RegisterUseCase registerUseCase;

  @Test
  void testRegister(a){
    // given
    User user = new User("Zaphod"."[email protected]");
    boolean sendWelcomeMail = true;
    given(userRepository.save(any(UserEntity.class))).willReturn(userEntity(1L));

    // when
    Long userId = registerUseCase.registerUser(user, sendWelcomeMail);

    // then
    assertThat(userId).isEqualTo(1L); }}Copy the code

In this case, we have replaced the UserRepository bean with emulation. Using the Given method of Mockito, we specify the expected behavior of this simulation to test classes that use this repository. You can read more about the @MockBean annotation in my article on emulation.

Add beans using @import

If some beans are not included in the default application context, but we need them for testing, 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(a) { assertThat(foo).isNotNull(); }}Copy the code

By default, a Spring Boot application contains all components it finds in its packages and subpackages, so we usually only need to do this if we want to include beans from other packages.

Override the Bean with @testConfiguration

Using @TestConfiguration, we can not only include additional beans needed for testing, but also override beans already defined in the application. Read more about this in our article on testing with @TestConfiguration.

Create a custom @SpringBootApplication

We can even create a full 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 rather than 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 {}Copy the code

However, when we do this, we are testing an application context that may be completely different from the production environment, so this should only be a last resort if the production application cannot be started in the test environment. However, there is usually a better approach, such as making the real application context configurable to exclude beans that will not be started in the test environment. Let’s look at an example. Suppose we use the @enablesCheduling annotation on our application class. Every time the application context is started (even during testing), all @Scheduled jobs will start and may conflict with our tests. We generally don’t want jobs to run in tests, so we can create a second application class without the @EnabledScheduling annotation and use it in our tests. However, a better solution would be to create a configuration class that can use property toggling:

@Configuration
@EnableScheduling
@ConditionalOnProperty( name = "io.reflectoring.scheduling.enabled", havingValue = "true", matchIfMissing = true)
public class SchedulingConfiguration {}Copy the code

We have moved the @EnablesCheduling annotation from our application class to this particular configuration class. Attribute IO. Reflectoring. Scheduling. Enabled set to false will lead to such not as part of the application context load:

@SpringBootTest(properties = "io.reflectoring.scheduling.enabled=false")
class SchedulingTest {

  @Autowired(required = false)
  private SchedulingConfiguration schedulingConfiguration;

  @Test
  void test(a) { assertThat(schedulingConfiguration).isNull(); }}Copy the code

We have now successfully deactivated the scheduled job in the test. Attribute IO. Reflectoring. Scheduling. Enabled can be specified through the above any way.

Why are my integration tests so slow?

A code base that contains a large number of @Springboottest annotation tests can take quite a while to run. Spring’s testing support is smart enough to only create an application context once and reuse it in subsequent tests, but if different tests require different application contexts, it still creates a separate context for each test, which takes some time to complete each test. All of the customization options described above cause Spring to create a new application context. Therefore, we might want to create a configuration and use it for all tests so that the application context can be reused. If you’re interested in testing the amount of time spent on setup and Spring application context, you might want to check out JUnit Insights, which can be included in a Gradle or Maven build to produce a good report on how JUnit 5 spent its time.

conclusion

SpringBootTest @Springboottest is a very handy way to set up an application context for testing that is very close to the context we will use in production. There are many options for customizing this application context, but they should be used with caution because we want our tests to be as close to a production run as possible. If we want to test across the entire application, @Springboottest brings the most value. There are other options available to test only certain slices or layers of the application. The sample code used in this article is available on Github.