Spring Boot 中的测试

更新于 2025-12-30

baeldung 2017-04-26

1. 概述

在本教程中,我们将了解如何使用 Spring Boot 提供的框架支持来编写测试。我们将涵盖可以独立运行的单元测试,以及在执行测试前需要启动 Spring 上下文的集成测试。

2. 项目设置

本文将使用的应用程序是一个提供对 Employee(员工)资源进行基本操作的 API。这是一个典型的分层架构 —— API 请求从 Controller 层传递到 Service 层,再到持久化(Persistence)层。

3. Maven 依赖

首先添加我们的测试依赖:

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

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

H2 数据库作为内存数据库,无需配置和启动真实数据库即可用于测试。

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

顾名思义,集成测试关注的是应用程序不同层级之间的集成。这意味着不会使用任何 Mock(模拟)对象。

理想情况下,我们应该将集成测试与单元测试分开,并避免一起运行。可以通过使用不同的 Profile 来仅运行集成测试。这样做的原因可能包括:集成测试耗时较长,或者需要真实的数据库环境。

不过在本文中,我们不会重点讨论这一点,而是使用内存中的 H2 数据库。

集成测试需要启动容器来执行测试用例,因此需要一些额外的设置 —— 而在 Spring Boot 中,这一切都非常简单:

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

    @Autowired
    private MockMvc mvc;

    @Autowired
    private EmployeeRepository repository;

    // 在此处编写测试用例
}
  • @SpringBootTest 注解用于需要启动整个应用上下文的场景。该注解会创建一个 ApplicationContext,供测试使用。
  • webEnvironment 属性用于配置运行时环境;这里使用 WebEnvironment.MOCK,使容器在模拟的 Servlet 环境中运行。
  • @TestPropertySource 注解用于指定测试专用的属性文件位置。注意,通过此注解加载的属性文件会覆盖原有的 application.properties 文件。

application-integrationtest.properties 文件包含用于配置持久化存储的详细信息:

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

如果希望针对 MySQL 运行集成测试,只需修改上述属性值即可。

集成测试的测试用例可能与 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")));
}

与 Controller 层单元测试的区别在于:这里没有任何 Mock,执行的是端到端的完整流程。

5. 使用 @TestConfiguration 进行测试配置

如前一节所示,使用 @SpringBootTest 注解的测试会启动完整的应用上下文,这意味着我们可以将任何通过组件扫描发现的 Bean 通过 @Autowired 注入到测试类中:

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class EmployeeServiceImplIntegrationTest {

    @Autowired
    private EmployeeService employeeService;

    // 类代码 ...
}

然而,有时我们可能希望避免启动真实的应用上下文,而使用专门的测试配置。这时可以使用 @TestConfiguration 注解。有两种使用方式:

方式一:在测试类中定义静态内部类

@ExtendWith(SpringExtension.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
        @Bean
        public EmployeeService employeeService() {
            return new EmployeeService() {
                // 实现方法
            };
        }
    }

    @Autowired
    private EmployeeService employeeService;
}

方式二:创建独立的测试配置类

@TestConfiguration
public class EmployeeServiceImplTestContextConfiguration {
    
    @Bean
    public EmployeeService employeeService() {
        return new EmployeeService() { 
            // 实现方法 
        };
    }
}

注意:使用 @TestConfiguration 注解的配置类会被排除在组件扫描之外。因此,我们需要在每个需要注入该 Bean 的测试类中显式导入它,可通过 @Import 注解实现:

@ExtendWith(SpringExtension.class)
@Import(EmployeeServiceImplTestContextConfiguration.class)
public class EmployeeServiceImplIntegrationTest {

    @Autowired
    private EmployeeService employeeService;

    // 其余类代码
}

6. 使用 @MockBean 进行 Mock

我们的 Service 层代码依赖于 Repository:

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

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

然而,在测试 Service 层时,我们无需关心持久化层的具体实现。理想情况下,我们应该能够在不连接完整持久化层的情况下编写和测试 Service 层代码。

为此,我们可以使用 Spring Boot Test 提供的 Mock 支持。

首先看测试类的基本结构:

@ExtendWith(SpringExtension.class)
public class EmployeeServiceImplIntegrationTest {

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

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

    // 在此处编写测试用例
}

为了测试 Service 类,我们需要一个可用的 Service 实例(作为 @Bean),以便在测试类中通过 @Autowired 注入。这可以通过 @TestConfiguration 实现。

另一个关键点是 @MockBean 的使用。它会为 EmployeeRepository 创建一个 Mock 对象,从而绕过对真实 EmployeeRepository 的调用:

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

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

完成上述设置后,测试用例就变得非常简单:

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

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

我们将使用一个名为 Employee 的实体,它包含 idname 两个属性:

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

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

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

    // 标准的 getter、setter 和构造函数
}

以下是使用 Spring Data JPA 的 Repository 接口:

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

    public Employee findByName(String name);

}

持久化层代码到此为止。现在开始编写测试类。

首先创建测试类的基本结构:

@ExtendWith(SpringExtension.class)
@DataJpaTest
public class EmployeeRepositoryIntegrationTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private EmployeeRepository employeeRepository;

    // 在此处编写测试用例
}
  • @ExtendWith(SpringExtension.class) 在 Spring Boot 测试功能与 JUnit 之间建立桥梁。只要在 JUnit 测试中使用了 Spring Boot 的测试特性,就需要此注解。
  • @DataJpaTest 为持久化层测试提供了一些标准配置:
    • 配置 H2 内存数据库
    • 设置 Hibernate、Spring Data 和 DataSource
    • 执行 @EntityScan
    • 启用 SQL 日志

要执行数据库操作,我们需要数据库中已存在一些记录。为此,可以使用 TestEntityManager

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

EmployeeRepository 是我们要测试的组件。

现在编写第一个测试用例:

@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());
}

在上述测试中,我们使用 TestEntityManager 向数据库插入一个 Employee,并通过按名称查找的 API 读取它。

assertThat(...) 来自 AssertJ 库,该库已随 Spring Boot 一起提供。

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

我们的 Controller 依赖于 Service 层;为简化起见,只包含一个方法:

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

    @Autowired
    private EmployeeService employeeService;

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

由于我们只关注 Controller 代码,因此在单元测试中自然应该 Mock Service 层:

@ExtendWith(SpringExtension.class)
@WebMvcTest(EmployeeRestController.class)
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private EmployeeService service;

    // 在此处编写测试用例
}
  • 使用 @WebMvcTest 可以自动配置 Spring MVC 基础设施,用于单元测试。
  • 通常,@WebMvcTest 仅用于启动单个 Controller。也可以结合 @MockBean 为所需依赖提供 Mock 实现。
  • @WebMvcTest 还会自动配置 MockMvc,它提供了一种强大且便捷的方式来测试 MVC 控制器,而无需启动完整的 HTTP 服务器。

接下来编写测试用例:

@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())));
}

我们可以将 get(...) 方法替换为对应其他 HTTP 动词的方法,如 put()post() 等。注意,我们还在请求中设置了内容类型。

MockMvc 非常灵活,可以构造任意类型的请求。

9. 自动配置的测试

Spring Boot 的自动配置注解的一大优势在于,它能只加载应用程序的部分组件,从而针对性地测试代码库的特定层级。

除了上述提到的注解外,以下是一些广泛使用的注解:

注解 描述
@WebFluxTest 用于测试 Spring WebFlux 控制器,通常与 @MockBean 结合使用以提供依赖的 Mock 实现。
@JdbcTest 用于仅需 DataSource 的 JPA 应用测试,会配置内存嵌入式数据库和 JdbcTemplate
@JooqTest 用于测试 jOOQ 相关组件,会配置 DSLContext
@DataMongoTest 用于测试 MongoDB 应用,若驱动可用则配置内存嵌入式 MongoDB、MongoTemplate,扫描 @Document 类并配置 Spring Data MongoDB Repository。
@DataRedisTest 用于 Redis 应用测试,默认扫描 @RedisHash 类并配置 Spring Data Redis Repository。
@DataLdapTest 配置内存嵌入式 LDAP(若可用)、LdapTemplate,扫描 @Entry 类并配置 Spring Data LDAP Repository。
@RestClientTest 用于测试 REST 客户端,自动配置 Jackson/Gson/Jsonb 支持,配置 RestTemplateBuilder,并默认支持 MockRestServiceServer
@JsonTest 仅初始化测试 JSON 序列化所需的 Spring 应用上下文 Bean。

更多关于这些注解的信息,以及如何进一步优化集成测试,请参阅我们关于《优化 Spring 集成测试》的文章。

10. 结论

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