跳到主要内容

Testcontainers

DeepSeek V3 中英对照 Testcontainers

Testcontainers 库提供了一种管理在 Docker 容器内运行的服务的方式。它与 JUnit 集成,允许你编写一个测试类,在运行任何测试之前启动一个容器。Testcontainers 特别适用于编写与真实后端服务(如 MySQL、MongoDB、Cassandra 等)通信的集成测试。

Testcontainers 可以在 Spring Boot 测试中按以下方式使用:

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.boot.test.context.SpringBootTest;

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

@Container
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");

@Test
void myTest() {
// ...
}

}
java

这将在运行任何测试之前启动一个运行 Neo4j 的 Docker 容器(如果本地正在运行 Docker)。在大多数情况下,你需要配置应用程序以连接到容器中运行的服务。

服务连接

服务连接是指与任何远程服务的连接。Spring Boot 的自动配置可以利用服务连接的详细信息,并使用它们来建立与远程服务的连接。在这种情况下,连接详细信息优先于任何与连接相关的配置属性。

在使用 Testcontainers 时,可以通过在测试类中注解容器字段来自动创建运行在容器中的服务的连接详细信息。

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

@Container
@ServiceConnection
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");

@Test
void myTest() {
// ...
}

}
java

感谢 @ServiceConnection,上述配置使得应用程序中与 Neo4j 相关的 Bean 能够与运行在 Testcontainers 管理的 Docker 容器中的 Neo4j 进行通信。这是通过自动定义一个 Neo4jConnectionDetails Bean 来实现的,随后该 Bean 被 Neo4j 自动配置使用,覆盖任何与连接相关的配置属性。

备注

你需要将 spring-boot-testcontainers 模块作为测试依赖项添加,以便使用 Testcontainers 的服务连接。

服务连接注解通过注册在 spring.factories 中的 ContainerConnectionDetailsFactory 类进行处理。ContainerConnectionDetailsFactory 可以根据特定的 Container 子类或 Docker 镜像名称创建 ConnectionDetails bean。

以下服务连接工厂在 spring-boot-testcontainers jar 中提供:

连接详情匹配于
ActiveMQConnectionDetails名为 "symptoma/activemq" 的容器或 ActiveMQContainer
ArtemisConnectionDetails类型为 ArtemisContainer 的容器
CassandraConnectionDetails容器类型为 CassandraContainer
CouchbaseConnectionDetailsCouchbaseContainer 类型的容器
ElasticsearchConnectionDetails容器类型为 ElasticsearchContainer
FlywayConnectionDetails类型为 JdbcDatabaseContainer 的容器
JdbcConnectionDetails类型为 JdbcDatabaseContainer 的容器
KafkaConnectionDetails类型为 KafkaContainerConfluentKafkaContainerRedpandaContainer 的容器
LiquibaseConnectionDetails类型为 JdbcDatabaseContainer 的容器
MongoConnectionDetails类型为 MongoDBContainer 的容器
Neo4j 连接详情类型为 Neo4jContainer 的容器
OtlpLoggingConnectionDetails容器名称为 "otel/opentelemetry-collector-contrib" 或类型为 LgtmStackContainer
OtlpMetricsConnectionDetails容器名称为 "otel/opentelemetry-collector-contrib" 或类型为 LgtmStackContainer
OtlpTracingConnectionDetails容器名称为 "otel/opentelemetry-collector-contrib" 或类型为 LgtmStackContainer
PulsarConnectionDetails类型为 PulsarContainer 的容器
R2dbcConnectionDetails容器类型包括 ClickHouseContainerMariaDBContainerMSSQLServerContainerMySQLContainerOracleContainer (free)OracleContainer (XE)PostgreSQLContainer
RabbitConnectionDetails类型为 RabbitMQContainer 的容器
RedisConnectionDetails容器类型为 RedisContainerRedisStackContainer,或者容器名称为 "redis"、"redis/redis-stack" 或 "redis/redis-stack-server"。
ZipkinConnectionDetails容器名称为 "openzipkin/zipkin"
提示

默认情况下,所有适用的连接详细信息 bean 都会为给定的 Container 创建。例如,一个 PostgreSQLContainer 将同时创建 JdbcConnectionDetailsR2dbcConnectionDetails

如果你只想创建适用的类型中的一部分,可以使用 @ServiceConnectiontype 属性。

默认情况下,Container.getDockerImageName().getRepository() 用于获取用于查找连接详情的名称。Docker 镜像名称的仓库部分会忽略任何注册表和版本信息。只要 Spring Boot 能够获取到 Container 的实例,这种方式就可以正常工作,例如在上面的示例中使用 static 字段时就是如此。

如果你使用的是 @Bean 方法,Spring Boot 不会调用该 bean 方法来获取 Docker 镜像名称,因为这会导致提前初始化问题。相反,Spring Boot 会使用 bean 方法的返回类型来确定应该使用哪个连接细节。只要你在使用类型化的容器(如 Neo4jContainerRabbitMQContainer),这种方式就能正常工作。但如果你使用的是 GenericContainer,例如像下面示例中的 Redis,这种方式就不再有效了:

import org.testcontainers.containers.GenericContainer;

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

@TestConfiguration(proxyBeanMethods = false)
public class MyRedisConfiguration {

@Bean
@ServiceConnection(name = "redis")
public GenericContainer<?> redisContainer() {
return new GenericContainer<>("redis:7");
}

}
java

Spring Boot 无法从 GenericContainer 中判断使用的是哪个容器镜像,因此必须使用 @ServiceConnectionname 属性来提供提示。

你也可以使用 @ServiceConnectionname 属性来覆盖将使用哪个连接详细信息,例如在使用自定义镜像时。如果你使用的是 Docker 镜像 registry.mycompany.com/mirror/myredis,你可以使用 @ServiceConnection(name="redis") 来确保创建 RedisConnectionDetails

动态属性

与服务连接相比,@DynamicPropertySource 是一个稍显冗长但更为灵活的替代方案。静态的 @DynamicPropertySource 方法允许向 Spring 环境中添加动态属性值。

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

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

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

@Container
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");

@Test
void myTest() {
// ...
}

@DynamicPropertySource
static void neo4jProperties(DynamicPropertyRegistry registry) {
registry.add("spring.neo4j.uri", neo4j::getBoltUrl);
}

}
java

上述配置允许应用程序中与 Neo4j 相关的 Bean 与在 Testcontainers 管理的 Docker 容器内运行的 Neo4j 进行通信。