此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Boot 3.4.5spring-doc.cadn.net.cn

测试容器

Testcontainers 库提供了一种管理在 Docker 容器中运行的服务的方法。 它与 JUnit 集成,允许您编写一个测试类,该类可以在任何测试运行之前启动容器。 Testcontainers 对于编写与实际后端服务(如 MySQL、MongoDB、Cassandra 等)通信的集成测试特别有用。spring-doc.cadn.net.cn

在以下部分中,我们将介绍一些可用于将 Testcontainers 与测试集成的方法。spring-doc.cadn.net.cn

使用 Spring Bean

Testcontainers 提供的容器可以由 Spring Boot 作为 bean 进行管理。spring-doc.cadn.net.cn

要将容器声明为 Bean,请添加@Bean方法添加到您的测试配置中:spring-doc.cadn.net.cn

import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.utility.DockerImageName;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

@TestConfiguration(proxyBeanMethods = false)
class MyTestConfiguration {

	@Bean
	MongoDBContainer mongoDbContainer() {
		return new MongoDBContainer(DockerImageName.parse("mongo:5.0"));
	}

}
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.testcontainers.containers.MongoDBContainer
import org.testcontainers.utility.DockerImageName

@TestConfiguration(proxyBeanMethods = false)
class MyTestConfiguration {

	@Bean
	fun mongoDbContainer(): MongoDBContainer {
		return MongoDBContainer(DockerImageName.parse("mongo:5.0"))
	}

}

然后,您可以通过在 test 类中导入配置类来注入和使用容器:spring-doc.cadn.net.cn

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MongoDBContainer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

@SpringBootTest
@Import(MyTestConfiguration.class)
class MyIntegrationTests {

	@Autowired
	private MongoDBContainer mongo;

	@Test
	void myTest() {
		...
	}

}
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.Import
import org.testcontainers.containers.MongoDBContainer

@SpringBootTest
@Import(MyTestConfiguration::class)
class MyIntegrationTests {

	@Autowired
	private val mongo: MongoDBContainer? = null

	@Test
	fun myTest() {
		...
	}

}
这种管理容器的方法通常与服务连接注释结合使用。

使用 JUnit 扩展

Testcontainers 提供了一个 JUnit 扩展,可用于管理测试中的容器。 扩展是通过应用@Testcontainers注解从 Testcontainers 添加到您的测试类中。spring-doc.cadn.net.cn

然后,您可以使用@Container对静态容器字段的注释。spring-doc.cadn.net.cn

@Testcontainers注解可以用于 vanilla JUnit 测试,也可以与@SpringBootTest:spring-doc.cadn.net.cn

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() {
		...
	}

}
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 {

	@Test
	fun myTest() {
		...
	}

	companion object {

		@Container
		@JvmStatic
		val neo4j = Neo4jContainer("neo4j:5");

	}
}

上面的示例将在运行任何测试之前启动一个 Neo4j 容器。 容器实例的生命周期由 Testcontainers 管理,如其官方文档中所述。spring-doc.cadn.net.cn

在大多数情况下,您还需要配置应用程序以连接到容器中运行的服务。

导入容器配置接口

Testcontainers 的常见模式是将容器实例声明为接口中的静态字段。spring-doc.cadn.net.cn

例如,以下接口声明了两个容器,一个名为mongo的类型MongoDBContainer另一个名为neo4j的类型Neo4jContainer:spring-doc.cadn.net.cn

import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;

interface MyContainers {

	@Container
	MongoDBContainer mongoContainer = new MongoDBContainer("mongo:5.0");

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

}
import org.testcontainers.containers.MongoDBContainer
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container

interface MyContainers {

	companion object {

		@Container
		val mongoContainer: MongoDBContainer = MongoDBContainer("mongo:5.0")

		@Container
		val neo4jContainer: Neo4jContainer<*> = Neo4jContainer("neo4j:5")

	}

}

当您以这种方式声明容器时,您可以通过让测试类实现接口,在多个测试中重用它们的配置。spring-doc.cadn.net.cn

也可以在 Spring Boot 测试中使用相同的接口配置。 为此,请添加@ImportTestcontainers添加到您的测试配置类中:spring-doc.cadn.net.cn

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.context.ImportTestcontainers;

@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers.class)
class MyTestConfiguration {

}
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.context.ImportTestcontainers

@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers::class)
class MyTestConfiguration {

}

托管容器的生命周期

如果您使用了 Testcontainers 提供的注释和扩展,则容器实例的生命周期完全由 Testcontainers 管理。 有关信息,请参阅官方 Testcontainers 文档spring-doc.cadn.net.cn

当容器由 Spring 作为 bean 管理时,它们的生命周期由 Spring 管理:spring-doc.cadn.net.cn

此过程可确保任何依赖于容器提供的功能的 bean 都可以使用这些功能。 它还确保在容器仍然可用时清理它们。spring-doc.cadn.net.cn

当应用程序 Bean 依赖于容器的功能时,最好将容器配置为 Spring Bean,以确保正确的生命周期行为。
让容器由 Testcontainers 而不是 Spring bean 管理不能保证 bean 和容器的关闭顺序。 在清理依赖容器功能的 bean 之前,容器可能会关闭。 这可能会导致客户端 bean 引发异常,例如,由于连接丢失。

容器 bean 在 Spring 的 TestContext Framework 管理的每个应用程序上下文中创建和启动一次。 有关 TestContext Framework 如何管理底层应用程序上下文和其中的 bean 的详细信息,请参阅 Spring Framework 文档spring-doc.cadn.net.cn

容器 bean 作为 TestContext Framework 的标准应用程序上下文关闭过程的一部分而停止。 当应用程序上下文关闭时,容器也会关闭。 这通常发生在使用该特定缓存应用程序上下文的所有测试完成执行之后。 它也可能会更早发生,具体取决于 TestContext Framework 中配置的缓存行为。spring-doc.cadn.net.cn

单个测试容器实例可以而且经常在执行来自多个测试类的测试时保留。

服务连接

服务连接是与任何远程服务的连接。 Spring Boot 的自动配置可以使用服务连接的详细信息,并使用它们来建立与远程服务的连接。 执行此作时,连接详细信息优先于任何与连接相关的配置属性。spring-doc.cadn.net.cn

使用 Testcontainers 时,可以通过在 test 类中注释 container 字段,为容器中运行的服务自动创建连接详细信息。spring-doc.cadn.net.cn

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() {
		...
	}

}
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 {

	@Test
	fun myTest() {
		...
	}

	companion object {

		@Container
		@ServiceConnection
		@JvmStatic
		val neo4j = Neo4jContainer("neo4j:5");

	}

}

由于@ServiceConnection,上述配置允许应用程序中与 Neo4j 相关的 bean 与在 Testcontainers 管理的 Docker 容器内运行的 Neo4j 进行通信。 这是通过自动定义Neo4jConnectionDetailsbean,然后由 Neo4j 自动配置使用,覆盖任何与连接相关的配置属性。spring-doc.cadn.net.cn

您需要添加spring-boot-testcontainersmodule 作为测试依赖项,以便将服务连接与 Testcontainers 一起使用。

服务连接注释由ContainerConnectionDetailsFactory注册的类spring.factories. 一个ContainerConnectionDetailsFactory可以创建一个ConnectionDetailsbean 基于特定的Container子类或 Docker 镜像名称。spring-doc.cadn.net.cn

以下服务连接工厂在spring-boot-testcontainers罐:spring-doc.cadn.net.cn

连接详细信息 匹配时间

ActiveMQConnectionDetailsspring-doc.cadn.net.cn

名为 “symptoma/activemq” 的容器或ActiveMQContainerspring-doc.cadn.net.cn

ArtemisConnectionDetailsspring-doc.cadn.net.cn

类型的容器ArtemisContainerspring-doc.cadn.net.cn

CassandraConnectionDetailsspring-doc.cadn.net.cn

类型的容器CassandraContainerspring-doc.cadn.net.cn

CouchbaseConnectionDetailsspring-doc.cadn.net.cn

类型的容器CouchbaseContainerspring-doc.cadn.net.cn

ElasticsearchConnectionDetailsspring-doc.cadn.net.cn

类型的容器ElasticsearchContainerspring-doc.cadn.net.cn

FlywayConnectionDetailsspring-doc.cadn.net.cn

类型的容器JdbcDatabaseContainerspring-doc.cadn.net.cn

JdbcConnectionDetailsspring-doc.cadn.net.cn

类型的容器JdbcDatabaseContainerspring-doc.cadn.net.cn

KafkaConnectionDetailsspring-doc.cadn.net.cn

类型的容器KafkaContainer,ConfluentKafkaContainerRedpandaContainerspring-doc.cadn.net.cn

LdapConnectionDetailsspring-doc.cadn.net.cn

名为 “osixia/openldap” 或LLdapContainerspring-doc.cadn.net.cn

LiquibaseConnectionDetailsspring-doc.cadn.net.cn

类型的容器JdbcDatabaseContainerspring-doc.cadn.net.cn

MongoConnectionDetailsspring-doc.cadn.net.cn

类型的容器MongoDBContainerspring-doc.cadn.net.cn

Neo4jConnectionDetailsspring-doc.cadn.net.cn

类型的容器Neo4jContainerspring-doc.cadn.net.cn

OtlpLoggingConnectionDetailsspring-doc.cadn.net.cn

名为 “otel/opentelemetry-collector-contrib” 或LgtmStackContainerspring-doc.cadn.net.cn

OtlpMetricsConnectionDetailsspring-doc.cadn.net.cn

名为 “otel/opentelemetry-collector-contrib” 或LgtmStackContainerspring-doc.cadn.net.cn

OtlpTracingConnectionDetailsspring-doc.cadn.net.cn

名为 “otel/opentelemetry-collector-contrib” 或LgtmStackContainerspring-doc.cadn.net.cn

PulsarConnectionDetailsspring-doc.cadn.net.cn

类型的容器PulsarContainerspring-doc.cadn.net.cn

R2dbcConnectionDetailsspring-doc.cadn.net.cn

类型的容器ClickHouseContainer,MariaDBContainer,MSSQLServerContainer,MySQLContainerOracleContainer(免费)、OracleContainer (XE)PostgreSQLContainerspring-doc.cadn.net.cn

RabbitConnectionDetailsspring-doc.cadn.net.cn

类型的容器RabbitMQContainerspring-doc.cadn.net.cn

RedisConnectionDetailsspring-doc.cadn.net.cn

类型的容器RedisContainerRedisStackContainer或名为 “redis”、“redis/redis-stack” 或 “redis/redis-stack-server” 的容器spring-doc.cadn.net.cn

ZipkinConnectionDetailsspring-doc.cadn.net.cn

名为 “openzipkin/zipkin” 的容器spring-doc.cadn.net.cn

默认情况下,将为给定的 bean 创建所有适用的连接详细信息 beanContainer. 例如,PostgreSQLContainer将同时创建JdbcConnectionDetailsR2dbcConnectionDetails.spring-doc.cadn.net.cn

如果只想创建适用类型的子集,可以使用type属性@ServiceConnection.spring-doc.cadn.net.cn

默认情况下Container.getDockerImageName().getRepository()用于获取用于查找连接详细信息的名称。 Docker 镜像名称的存储库部分会忽略任何注册表和版本。 只要 Spring Boot 能够获取Container,当使用static字段。spring-doc.cadn.net.cn

如果您使用的是@Bean方法,Spring Boot 不会调用 bean 方法来获取 Docker 镜像名称,因为这会导致急切初始化问题。 相反,使用 bean 方法的返回类型来找出应该使用哪个连接详细信息。 只要你使用的是类型化容器,例如Neo4jContainerRabbitMQContainer. 如果您正在使用GenericContainer,例如使用 Redis,如以下示例所示:spring-doc.cadn.net.cn

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");
	}

}
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.GenericContainer

@TestConfiguration(proxyBeanMethods = false)
class MyRedisConfiguration {

	@Bean
	@ServiceConnection(name = "redis")
	fun redisContainer(): GenericContainer<*> {
		return GenericContainer("redis:7")
	}

}

Spring Boot 无法判断GenericContainer使用哪个容器镜像,那么name属性从@ServiceConnection必须用于提供该提示。spring-doc.cadn.net.cn

您还可以使用name属性@ServiceConnection覆盖将使用的连接详细信息,例如,在使用自定义图像时。 如果您使用的是 Docker 镜像registry.mycompany.com/mirror/myredis,您将使用@ServiceConnection(name="redis")确保RedisConnectionDetails创建。spring-doc.cadn.net.cn

使用服务连接的 SSL

您可以使用@Ssl,@JksKeyStore,@JksTrustStore,@PemKeyStore@PemTrustStore注解,以便为该服务连接启用 SSL 支持。 请注意,您仍然必须在 Testcontainer 内部运行的服务上启用 SSL,注释仅在应用程序的客户端配置 SSL。spring-doc.cadn.net.cn

import com.redis.testcontainers.RedisContainer;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.PemKeyStore;
import org.springframework.boot.testcontainers.service.connection.PemTrustStore;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.data.redis.core.RedisOperations;

@Testcontainers
@SpringBootTest
class MyRedisWithSslIntegrationTests {

	@Container
	@ServiceConnection
	@PemKeyStore(certificate = "classpath:client.crt", privateKey = "classpath:client.key")
	@PemTrustStore("classpath:ca.crt")
	static RedisContainer redis = new SecureRedisContainer("redis:latest");

	@Autowired
	private RedisOperations<Object, Object> operations;

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

}

上面的代码使用了@PemKeyStore注解将客户端证书和密钥加载到密钥库中,并使用 和@PemTrustStore注解将 CA 证书加载到信任库中。 这将根据服务器对客户端进行身份验证,并且信任库中的 CA 证书确保服务器证书有效且可信。spring-doc.cadn.net.cn

SecureRedisContainer在此示例中是RedisContainer将证书复制到正确的位置并调用redis-server使用启用 SSL 的命令行参数。spring-doc.cadn.net.cn

以下服务连接支持 SSL 注释:spring-doc.cadn.net.cn

ElasticsearchContainer还支持服务器端 SSL 的自动检测。 要使用此功能,请使用@Ssl,如以下示例所示,Spring Boot 会为您处理客户端 SSL 配置:spring-doc.cadn.net.cn

import org.junit.jupiter.api.Test;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.elasticsearch.DataElasticsearchTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.boot.testcontainers.service.connection.Ssl;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;

@Testcontainers
@DataElasticsearchTest
class MyElasticsearchWithSslIntegrationTests {

	@Ssl
	@Container
	@ServiceConnection
	static ElasticsearchContainer elasticsearch = new ElasticsearchContainer(
			"docker.elastic.co/elasticsearch/elasticsearch:8.17.2");

	@Autowired
	private ElasticsearchTemplate elasticsearchTemplate;

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

}

动态属性

与服务连接相比,一个稍微详细但更灵活的替代方案是@DynamicPropertySource. 静态@DynamicPropertySourcemethod 允许向 Spring Environment 添加动态属性值。spring-doc.cadn.net.cn

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

}
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

	@Test
	fun myTest() {
		...
	}

	companion object {
		@Container
		@JvmStatic
		val neo4j = Neo4jContainer("neo4j:5");

		@DynamicPropertySource
		@JvmStatic
		fun neo4jProperties(registry: DynamicPropertyRegistry) {
			registry.add("spring.neo4j.uri") { neo4j.boltUrl }
		}
	}
}

上述配置允许应用程序中与 Neo4j 相关的 bean 与在 Testcontainers 管理的 Docker 容器内运行的 Neo4j 进行通信。spring-doc.cadn.net.cn