hello
发布于 2026-09-28 / 6 阅读
0

Spring Boot 4.1 测数据库,别再只用 H2:Testcontainers 起一个 PostgreSQL

Spring Boot 4.1 测数据库,别再只用 H2:Testcontainers 起一个 PostgreSQL

单元测试用 H2 跑得很快,但如果生产环境是 PostgreSQL,SQL 方言、数据类型和约束行为仍可能在上线时出现差异。针对数据访问层,比较直接的做法是让集成测试启动一个临时 PostgreSQL 容器。Spring Boot 的 @ServiceConnection 可以把容器连接信息交给自动配置,省去在测试里手工拼接 JDBC URL、用户名和密码。

本文以 Spring Boot 4.1.1、Testcontainers 2.0.5、PostgreSQL 17 为示例。Spring Boot 4.1.1 要求至少 Java 17;本地或 CI 还需要可用的容器运行环境。Spring Boot 4.1.1 管理的 Testcontainers 版本是 2.0.5,使用 Spring Boot parent 或 BOM 时,下面的依赖不必单独写版本。版本和依赖坐标可在官方依赖清单核对。

验证说明:下方配置和代码依据 Spring Boot 与 Testcontainers 官方文档编写,本稿尚未在真实项目中执行。发布前应在目标 JDK、Maven 和容器环境中运行命令,并核对输出。

1. 加入依赖

假设项目已经由 Spring Boot 4.1.1 parent 或 BOM 管理依赖版本,在 pom.xml 的 <dependencies> 中加入:

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

spring-boot-testcontainers 提供服务连接支持;JUnit 扩展负责启动和停止测试类中的容器。PostgreSQL 模块不会自动带入 JDBC 驱动,所以还要保留 org.postgresql:postgresql。已经使用 spring-boot-starter-data-jpa 的项目通常不必再加 JDBC starter,但其余测试依赖仍需检查。

2. 写一个最小集成测试

在 src/test/java/com/example/demo/PostgresConnectionTest.java 新建测试类。示例假定项目的 @SpringBootApplication 类位于 com.example.demo 或其父包,且没有其他必须连接的外部服务。

package com.example.demo;

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.springframework.jdbc.core.JdbcTemplate;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.postgresql.PostgreSQLContainer;

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

@Testcontainers
@SpringBootTest
class PostgresConnectionTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer postgres =
            new PostgreSQLContainer("postgres:17-alpine");

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    void connectsToPostgres() {
        Integer result = jdbcTemplate.queryForObject("select 1", Integer.class);
        assertThat(result).isEqualTo(1);
    }
}

这里的静态 @Container 字段会由 Testcontainers JUnit 扩展按测试类生命周期管理。@ServiceConnection 根据 PostgreSQLContainer 创建 JDBC 连接信息,Spring Boot 自动配置的数据源会优先使用它,因此这个示例不需要在 application-test.yml 写死端口。官方文档还说明,一个 PostgreSQL 容器可能生成多种连接信息;如果项目同时引入 JDBC 与 R2DBC,要检查实际注入的是哪一类连接。

3. 运行与判断结果

先确认容器运行环境可用,再执行测试:

docker info
./mvnw -Dtest=PostgresConnectionTest test

如果项目没有 Maven Wrapper,改用已安装的 mvn。首次执行通常需要拉取 postgres:17-alpine 镜像;测试通过的判断是 Maven 报告 1 个测试成功,而不是仅看到容器启动日志。CI 运行器同样要有兼容的容器环境,否则测试会在启动 PostgreSQL 之前失败。

常见失败点

  1. Could not find a valid Docker environment:先用 docker info 检查容器运行环境,别把容器不可用误判成 Spring 数据源配置错误。
  2. 找不到 PostgreSQL 驱动:确认 org.postgresql:postgresql 在测试运行时类路径中;Testcontainers PostgreSQL 模块本身不提供驱动。
  3. @ServiceConnection 未生效:确认加入了 spring-boot-testcontainers,且测试导入的是 org.springframework.boot.testcontainers.service.connection.ServiceConnection。
  4. 已有数据源配置干扰测试:检查项目中自定义 DataSource Bean、测试配置和多个数据源的选择逻辑。官方文档规定服务连接信息优先于对应的连接属性,但自定义配置仍可能改变装配结果。
  5. 沿用旧版 Testcontainers 代码:Testcontainers 2.x 的 PostgreSQL 模块坐标是 testcontainers-postgresql;示例使用 org.testcontainers.postgresql.PostgreSQLContainer,避免继续依赖已标记废弃的旧包名。

这套写法适合验证真实 PostgreSQL 连接以及依赖数据库行为的数据访问测试。它不会自动证明业务 SQL、迁移脚本和事务边界正确;下一步应在同一个容器里加入一条有业务价值的 Repository 或 JDBC 测试,并在 CI 中实际跑通。