首页 / Spring Boot 入门教程 / 集成测试与 Testcontainers

Spring Boot 入门教程

集成测试与 Testcontainers

本教程共 48 篇 · 第 41 篇 · 更新于 2026-08-13 · 约 4 分钟阅读

Spring Boot集成测试@DataJpaTestTestcontainersPostgreSQLDocker测试数据库

本节目标:掌握 @DataJpaTest 的用法与局限,会用 Testcontainers 起真实数据库容器做集成测试,理解 @ServiceConnection@DynamicPropertySource

从 Repository 测试说起

Web 层测完了,数据层怎么测?Repository 接口的方法名、@Query 注解的 SQL,都得验证。

Spring Boot 提供 @DataJpaTest 切片:只加载 JPA 相关组件。

  • 扫描 @Entity 实体类。
  • 配置 Spring Data JPA 的 Repository。
  • 类路径上有内嵌数据库就自动配置一个。
  • 每个测试默认走事务,跑完自动回滚,数据不残留。
package com.example.demo.book;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager;

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
class BookRepositoryTest {

    @Autowired
    private BookRepository bookRepository;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    void findByIsbnReturnsBook() {
        entityManager.persistAndFlush(new Book("Spring Boot 入门", "978-7-111-00001"));

        Optional<Book> found = bookRepository.findByIsbn("978-7-111-00001");

        assertThat(found).isPresent();
        assertThat(found.get().getTitle()).isEqualTo("Spring Boot 入门");
    }
}

TestEntityManager 是测试专用的 EntityManager,persistAndFlush 立即落库,确保查询能查到。

Note

4.x 的 @DataJpaTest 包名是 org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest,旧版 org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest 是 3.x 写法。

内嵌数据库的问题

@DataJpaTest 默认用 H2 内嵌库。测试飞快,不用装任何东西。但隐患藏在细节里:

  • H2 的方言和 MySQL / PostgreSQL 有差异。
  • 生产用的 SQL 特性,H2 可能不支持或行为不同。
  • 分页、锁、JSON 字段、全文索引,最容易踩坑。

内嵌库测试全绿,上了生产就报错,是经典事故。解法是用真实数据库测试。

Testcontainers 是什么

Testcontainers 是一个 Java 库:测试跑起来时,自动用 Docker 启动一个真实服务容器,测完自动销毁。数据库、Redis、Kafka 都能起。

先加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <scope>test</scope>
</dependency>

前提:本机装有 Docker,且 Docker 守护进程在运行。测试会先拉镜像,第一次稍慢,之后走本地缓存。

第一种接法:@ServiceConnection

Spring Boot 3.1 起提供 @ServiceConnection。给容器字段加上它,Spring Boot 自动读取容器的地址、账号、密码,配置好数据源:

package com.example.demo.book;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
class BookRepositoryContainerTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");

    @Autowired
    private BookRepository bookRepository;

    @Test
    void saveAndFindBook() {
        bookRepository.save(new Book("测试驱动开发", "978-7-111-00002"));

        assertThat(bookRepository.findByIsbn("978-7-111-00002")).isPresent();
    }
}

三个注解各干一件事:

  • @Testcontainers:启用 Testcontainers 的 JUnit 扩展,管理容器生命周期。
  • @Container:标记静态字段,类里所有测试跑之前启动容器,跑完停止。
  • @AutoConfigureTestDatabase(replace = Replace.NONE)关键。默认 @DataJpaTest 会换成内嵌数据库,这行告诉它别换,用 @ServiceConnection 提供的真实库。

第二种接法:@DynamicPropertySource

@ServiceConnection 覆盖不了所有场景(比如自定义容器、非标准端口)。这时用 @DynamicPropertySource 手动注入连接信息:

import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;

@Testcontainers
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class BookRepositoryDynamicTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");

    @DynamicPropertySource
    static void datasourceProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }
    // 测试方法略,和上面一样
}

registry.add 的第一个参数是配置键,第二个是值的提供者(等容器启动后才取值)。这招对任意属性都管用,比如 Redis 地址、Kafka 端口。

Note

@DynamicPropertySource 是 Spring Framework 提供的通用机制,不依赖 Testcontainers。容器启动后,Spring 环境里就多了这几个属性,优先级高于 application.yml

整应用集成测试:@SpringBootTest + 容器

Repository 测完,还可以把整个应用拉起来,用容器数据库跑端到端验证:

package com.example.demo.book;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@Testcontainers
class BookIntegrationTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");

    @Autowired
    private BookService bookService;

    @Test
    void serviceWorksWithRealDatabase() {
        Book saved = bookService.create(new Book("云原生 Java", "978-7-111-00003"));

        assertThat(saved.getId()).isNotNull();
    }
}

@ServiceConnection@SpringBootTest 下同样生效:数据源、JPA、Flyway 全部自动指向容器里的数据库。应用连的是真 PostgreSQL,测试价值直接拉满。

容器生命周期:谁管更靠谱

Testcontainers 的 JUnit 扩展(@Testcontainers + @Container)在测试类结束后就停容器。问题来了:Spring 会缓存应用上下文给多个测试类复用,上下文里的 Bean 还连着已停止的容器,后面再跑就可能报错。

官方建议:容器由 Spring 管理,而不是 JUnit 扩展。把容器声明成 Spring Bean:

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.PostgreSQLContainer;

@TestConfiguration(proxyBeanMethods = false)
class TestContainersConfiguration {

    @Bean
    @ServiceConnection
    PostgreSQLContainer<?> postgres() {
        return new PostgreSQLContainer<>("postgres:16-alpine");
    }
}

容器 Bean 会在其他 Bean 之前创建启动,在所有 Bean 销毁之后停止。需要它的测试类用 @Import(TestContainersConfiguration.class) 导入即可。

Warning

跑 Testcontainers 测试前确认 Docker 在运行。否则测试直接报 “Could not find a valid Docker environment”。CI 环境记得装 Docker 或配置好 Docker 服务。

小结

  • @DataJpaTest 用内嵌库测 Repository,快但方言有差异。
  • Testcontainers 用 Docker 起真实数据库,贴近生产。
  • @ServiceConnection 自动配置连接,@DynamicPropertySource 手动注入属性。
  • 容器最好交给 Spring 管理(Bean 方式),避免上下文缓存和容器生命周期冲突。