Testing in Spring Boot – 在Spring Boot中测试

最后修改: 2017年 4月 26日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll have a look at writing tests using the framework support in Spring Boot. We’ll cover unit tests that can run in isolation as well as integration tests that will bootstrap Spring context before executing tests.

在本教程中,我们将了解使用Spring Boot中的框架支持来编写测试。我们将介绍可以隔离运行的单元测试,以及在执行测试之前将引导Spring上下文的集成测试。

If you are new to Spring Boot, check out our intro to Spring Boot.

如果您是Spring Boot的新手,请查看我们的Spring Boot简介

2. Project Setup

2.项目设置

The application we’re going to use in this article is an API that provides some basic operations on an Employee Resource. This is a typical tiered architecture — the API call is processed from the Controller to Service to the Persistence layer.

我们在本文中要使用的应用程序是一个API,它提供了对Employee资源的一些基本操作。这是一个典型的分层架构–API调用从ControllerServicePersistence层进行处理。

3. Maven Dependencies

3.Maven的依赖性

Let’s first add our testing dependencies:

让我们首先添加我们的测试依赖性。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <version>2.5.0</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>

The spring-boot-starter-test is the primary dependency that contains the majority of elements required for our tests.

spring-boot-starter-test是主要的依赖关系,包含了我们测试所需的大部分元素。

The H2 DB is our in-memory database. It eliminates the need for configuring and starting an actual database for test purposes.

H2 DB是我们的内存式数据库。它消除了为测试目的而配置和启动一个实际数据库的需要。

3.1. JUnit 4

3.1.JUnit 4

Starting with Spring Boot 2.4, JUnit 5’s vintage engine has been removed from spring-boot-starter-test. If we still want to write tests using JUnit 4, we need to add the following Maven dependency:

从Spring Boot 2.4开始,JUnit 5的复古引擎已经从spring-boot-starter-test中移除。如果我们仍想使用JUnit 4编写测试,我们需要添加以下Maven依赖。

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

4. Integration Testing With @SpringBootTest

4.使用@SpringBootTest进行集成测试

As the name suggests, integration tests focus on integrating different layers of the application. That also means no mocking is involved.

顾名思义,集成测试的重点是集成应用程序的不同层。这也意味着不涉及嘲弄。

Ideally, we should keep the integration tests separated from the unit tests and should not run along with the unit tests. We can do this by using a different profile to only run the integration tests. A couple of reasons for doing this could be that the integration tests are time-consuming and might need an actual database to execute.

事实上,我们应该将集成测试与单元测试分开,不应该与单元测试一起运行。我们可以通过使用不同的配置文件只运行集成测试来做到这一点。这样做的几个原因可能是,集成测试很耗时,而且可能需要一个实际的数据库来执行。

However in this article, we won’t focus on that, and we’ll instead make use of the in-memory H2 persistence storage.

然而在这篇文章中,我们将不关注这个问题,而是利用内存中的H2持久化存储。

The integration tests need to start up a container to execute the test cases. Hence, some additional setup is required for this — all of this is easy in Spring Boot:

集成测试需要启动一个容器来执行测试案例。因此,需要为此进行一些额外的设置 – 所有这些在Spring Boot中都很容易。

@RunWith(SpringRunner.class)
@SpringBootTest(
  SpringBootTest.WebEnvironment.MOCK,
  classes = Application.class)
@AutoConfigureMockMvc
@TestPropertySource(
  locations = "classpath:application-integrationtest.properties")
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private EmployeeRepository repository;

    // write test cases here
}

The @SpringBootTest annotation is useful when we need to bootstrap the entire container. The annotation works by creating the ApplicationContext that will be utilized in our tests.

当我们需要引导整个容器时, @SpringBootTest注解非常有用。该注解通过创建将在我们的测试中使用的ApplicationContext来工作。

We can use the webEnvironment attribute of @SpringBootTest to configure our runtime environment; we’re using WebEnvironment.MOCK here so that the container will operate in a mock servlet environment.

我们可以使用@SpringBootTestwebEnvironment属性来配置我们的运行时环境;我们在这里使用WebEnvironment.MOCK,以便容器在模拟Servlet环境中运行。

Next, the @TestPropertySource annotation helps configure the locations of properties files specific to our tests. Note that the property file loaded with @TestPropertySource will override the existing application.properties file.

接下来, @TestPropertySource 注解有助于配置特定于我们测试的属性文件的位置。注意,用@TestPropertySource加载的属性文件将覆盖现有的application.properties文件。

The application-integrationtest.properties contains the details to configure the persistence storage:

application-integrationtest.properties包含配置持久化存储的细节。

spring.datasource.url = jdbc:h2:mem:test
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect

If we want to run our integration tests against MySQL, we can change the above values in the properties file.

如果我们想针对MySQL运行我们的集成测试,我们可以在属性文件中改变上述值。

The test cases for the integration tests might look similar to the Controller layer unit tests:

集成测试的测试用例可能与Controller层单元测试相似。

@Test
public void givenEmployees_whenGetEmployees_thenStatus200()
  throws Exception {

    createTestEmployee("bob");

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(content()
      .contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
      .andExpect(jsonPath("$[0].name", is("bob")));
}

The difference from the Controller layer unit tests is that here nothing is mocked and end-to-end scenarios will be executed.

控制器层单元测试的不同之处在于,这里没有什么是模拟的,端到端的场景将被执行。

5. Test Configuration With @TestConfiguration

5.用@TestConfiguration测试配置

As we’ve seen in the previous section, a test annotated with @SpringBootTest will bootstrap the full application context, which means we can @Autowire any bean that’s picked up by component scanning into our test:

正如我们在上一节看到的,用@SpringBootTest注解的测试将引导完整的应用程序上下文,这意味着我们可以@Autowire任何被组件扫描到我们测试中的bean。

@RunWith(SpringRunner.class)
@SpringBootTest
public class EmployeeServiceImplIntegrationTest {

    @Autowired
    private EmployeeService employeeService;

    // class code ...
}

However, we might want to avoid bootstrapping the real application context but use a special test configuration. We can achieve this with the @TestConfiguration annotation. There are two ways of using the annotation. Either on a static inner class in the same test class where we want to @Autowire the bean:

然而,我们可能想避免引导真实的应用程序上下文,而是使用一个特殊的测试配置。我们可以通过@TestConfiguration注解来实现这一点。有两种方式使用该注解。一种是在我们想@Autowirebean的同一个测试类中的静态内类上。

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
        @Bean
        public EmployeeService employeeService() {
            return new EmployeeService() {
                // implement methods
            };
        }
    }

    @Autowired
    private EmployeeService employeeService;
}

Alternatively, we can create a separate test configuration class:

或者,我们可以创建一个单独的测试配置类。

@TestConfiguration
public class EmployeeServiceImplTestContextConfiguration {
    
    @Bean
    public EmployeeService employeeService() {
        return new EmployeeService() { 
            // implement methods 
        };
    }
}

Configuration classes annotated with @TestConfiguration are excluded from component scanning, therefore we need to import it explicitly in every test where we want to @Autowire it. We can do that with the @Import annotation:

@TestConfiguration注解的配置类被排除在组件扫描之外,因此我们需要在每个我们想要@Autowire它的测试中明确导入它。我们可以用@Import注解来做到这一点。

@RunWith(SpringRunner.class)
@Import(EmployeeServiceImplTestContextConfiguration.class)
public class EmployeeServiceImplIntegrationTest {

    @Autowired
    private EmployeeService employeeService;

    // remaining class code
}

6. Mocking With @MockBean

6.用@MockBean进行模拟

Our Service layer code is dependent on our Repository:

我们的服务层代码依赖于我们的存储库:

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public Employee getEmployeeByName(String name) {
        return employeeRepository.findByName(name);
    }
}

However, to test the Service layer, we don’t need to know or care about how the persistence layer is implemented. Ideally, we should be able to write and test our Service layer code without wiring in our full persistence layer.

然而,为了测试Service层,我们不需要知道或关心持久层是如何实现的。理想情况下,我们应该能够编写和测试我们的服务层代码,而不需要在我们完整的持久层中布线。

To achieve this, we can use the mocking support provided by Spring Boot Test.

为了实现这一目标,我们可以使用Spring Boot Test提供的嘲弄支持。

Let’s have a look at the test class skeleton first:

让我们先看一下测试类的骨架。

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
 
        @Bean
        public EmployeeService employeeService() {
            return new EmployeeServiceImpl();
        }
    }

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

    // write test cases here
}

To check the Service class, we need to have an instance of the Service class created and available as a @Bean so that we can @Autowire it in our test class. We can achieve this configuration using the @TestConfiguration annotation.

为了检查Service类,我们需要创建一个Service类的实例,并作为@Bean可用,以便我们可以在测试类中@Autowire它。我们可以使用@TestConfiguration注解来实现这一配置。

Another interesting thing here is the use of @MockBean. It creates a Mock for the EmployeeRepository, which can be used to bypass the call to the actual EmployeeRepository:

这里另一个有趣的事情是使用@MockBean。它EmployeeRepository创建了一个Mock,它可以被用来绕过对实际EmployeeRepository的调用。

@Before
public void setUp() {
    Employee alex = new Employee("alex");

    Mockito.when(employeeRepository.findByName(alex.getName()))
      .thenReturn(alex);
}

Since the setup is done, the test case will be simpler:

由于设置已经完成,测试案例将更加简单。

@Test
public void whenValidName_thenEmployeeShouldBeFound() {
    String name = "alex";
    Employee found = employeeService.getEmployeeByName(name);
 
     assertThat(found.getName())
      .isEqualTo(name);
 }

7. Integration Testing With @DataJpaTest

7.使用@DataJpaTest进行集成测试

We’re going to work with an entity named Employee, which has an id and a name as its properties:

我们将使用一个名为Employee的实体,它有一个id和一个name作为其属性。

@Entity
@Table(name = "person")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Size(min = 3, max = 20)
    private String name;

    // standard getters and setters, constructors
}

And here’s our repository using Spring Data JPA:

这里是我们使用Spring Data JPA的存储库。

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

    public Employee findByName(String name);

}

That’s it for the persistence layer code. Now let’s head toward writing our test class.

持久层的代码就这样了。现在让我们开始编写我们的测试类。

First, let’s create the skeleton of our test class:

首先,让我们创建我们的测试类的骨架。

@RunWith(SpringRunner.class)
@DataJpaTest
public class EmployeeRepositoryIntegrationTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private EmployeeRepository employeeRepository;

    // write test cases here

}

@RunWith(SpringRunner.class) provides a bridge between Spring Boot test features and JUnit. Whenever we are using any Spring Boot testing features in our JUnit tests, this annotation will be required.

@RunWith(SpringRunner.class)在Spring Boot测试功能和JUnit之间提供了一座桥梁。每当我们在JUnit测试中使用任何Spring Boot测试功能时,都需要这个注解。

@DataJpaTest provides some standard setup needed for testing the persistence layer:

@DataJpaTest提供了一些测试持久化层所需的标准设置。

  • configuring H2, an in-memory database
  • setting Hibernate, Spring Data, and the DataSource
  • performing an @EntityScan
  • turning on SQL logging

To carry out DB operations, we need some records already in our database. To setup this data, we can use TestEntityManager.

为了进行DB操作,我们需要在我们的数据库中已有一些记录。为了设置这些数据,我们可以使用TestEntityManager.

The Spring Boot TestEntityManager is an alternative to the standard JPA EntityManager that provides methods commonly used when writing tests.

Spring Boot TestEntityManager是标准JPA EntityManager的替代品,它提供了编写测试时常用的方法。

EmployeeRepository is the component that we are going to test.

EmployeeRepository是我们要测试的组件。

Now let’s write our first test case:

现在我们来写第一个测试案例。

@Test
public void whenFindByName_thenReturnEmployee() {
    // given
    Employee alex = new Employee("alex");
    entityManager.persist(alex);
    entityManager.flush();

    // when
    Employee found = employeeRepository.findByName(alex.getName());

    // then
    assertThat(found.getName())
      .isEqualTo(alex.getName());
}

In the above test, we’re using the TestEntityManager to insert an Employee in the DB and reading it via the find by name API.

在上述测试中,我们使用TestEntityManager在DB中插入一个Employee,并通过find by name API读取它。

The assertThat(…) part comes from the Assertj library, which comes bundled with Spring Boot.

assertThat(…)部分来自Assertj库,它与Spring Boot绑定在一起。

8. Unit Testing With @WebMvcTest

8.使用@WebMvcTest进行单元测试

Our Controller depends on the Service layer; let’s only include a single method for simplicity:

我们的Controller依赖于Service层;为了简单起见,我们只包括一个方法。

@RestController
@RequestMapping("/api")
public class EmployeeRestController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/employees")
    public List<Employee> getAllEmployees() {
        return employeeService.getAllEmployees();
    }
}

Since we’re only focused on the Controller code, it’s natural to mock the Service layer code for our unit tests:

由于我们只关注Controller代码,所以在单元测试中自然要模拟Service层代码。

@RunWith(SpringRunner.class)
@WebMvcTest(EmployeeRestController.class)
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private EmployeeService service;

    // write test cases here
}

To test the Controllers, we can use @WebMvcTest. It will auto-configure the Spring MVC infrastructure for our unit tests.

为了测试Controllers,我们可以使用@WebMvcTest。它将为我们的单元测试自动配置Spring MVC基础设施。

In most cases, @WebMvcTest will be limited to bootstrap a single controller. We can also use it along with @MockBean to provide mock implementations for any required dependencies.

在大多数情况下,@WebMvcTest将被限制在引导一个单一的控制器。我们也可以将其与@MockBean一起使用,为任何需要的依赖提供模拟实现。

@WebMvcTest also auto-configures MockMvc, which offers a powerful way of easy testing MVC controllers without starting a full HTTP server.

@WebMvcTest还自动配置了MockMvc,它提供了一种强大的方式来轻松测试MVC控制器,而无需启动一个完整的HTTP服务器。

Having said that, let’s write our test case:

说了这么多,让我们来写我们的测试案例。

@Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray()
  throws Exception {
    
    Employee alex = new Employee("alex");

    List<Employee> allEmployees = Arrays.asList(alex);

    given(service.getAllEmployees()).willReturn(allEmployees);

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$", hasSize(1)))
      .andExpect(jsonPath("$[0].name", is(alex.getName())));
}

The get(…) method call can be replaced by other methods corresponding to HTTP verbs like put(), post(), etc. Please note that we are also setting the content type in the request.

get(…)方法的调用可以被其他对应于HTTP动词的方法所取代,如put(), post()等。请注意,我们也在请求中设置内容类型。

MockMvc is flexible, and we can create any request using it.

MockMvc很灵活,我们可以使用它创建任何请求。

9. Auto-Configured Tests

9.自动配置的测试

One of the amazing features of Spring Boot’s auto-configured annotations is that it helps to load parts of the complete application and test-specific layers of the codebase.

Spring Boot的自动配置注释的一个惊人的特点是,它有助于加载完整应用程序的部分内容和代码库的特定测试层。

In addition to the above-mentioned annotations, here’s a list of a few widely used annotations:

除了上面提到的注释外,这里还列出了一些广泛使用的注释。

  • @WebFluxTest: We can use the @WebFluxTest annotation to test Spring WebFlux controllers. It’s often used along with @MockBean to provide mock implementations for required dependencies.
  • @JdbcTest: We can use the @JdbcTest annotation to test JPA applications, but it’s for tests that only require a DataSource. The annotation configures an in-memory embedded database and a JdbcTemplate.
  • @JooqTest: To test jOOQ-related tests, we can use @JooqTest annotation, which configures a DSLContext.
  • @DataMongoTest: To test MongoDB applications, @DataMongoTest is a useful annotation. By default, it configures an in-memory embedded MongoDB if the driver is available through dependencies, configures a MongoTemplate, scans for @Document classes, and configures Spring Data MongoDB repositories.
  • @DataRedisTestmakes it easier to test Redis applications. It scans for @RedisHash classes and configures Spring Data Redis repositories by default.
  • @DataLdapTest configures an in-memory embedded LDAP (if available), configures a LdapTemplate, scans for @Entry classes, and configures Spring Data LDAP repositories by default.
  • @RestClientTest: We generally use the @RestClientTest annotation to test REST clients. It auto-configures different dependencies such as Jackson, GSON, and Jsonb support; configures a RestTemplateBuilder; and adds support for MockRestServiceServer by default.
  • @JsonTest: Initializes the Spring application context only with those beans needed to test JSON serialization.

You can read more about these annotations and how to further optimize integrations tests in our article Optimizing Spring Integration Tests.

您可以在我们的文章优化Spring集成测试中了解更多关于这些注释的信息以及如何进一步优化集成测试。

10. Conclusion

10.结论

In this article, we took a deep dive into the testing support in Spring Boot and showed how to write unit tests efficiently.

在这篇文章中,我们深入探讨了Spring Boot的测试支持,并展示了如何有效地编写单元测试。

The complete source code of this article can be found over on GitHub. The source code contains many more examples and various test cases.

本文的完整源代码可以在GitHub上找到。该源代码包含更多的示例和各种测试案例。

And if you want to keep learning about testing, we have separate articles related to integration tests, optimizing Spring integration tests, and unit tests in JUnit 5.

如果您想继续学习测试知识,我们还有与集成测试优化Spring集成测试JUnit 5中的单元测试相关的单独文章。