测试容器

Testcontainers 库提供了一种管理运行在 Docker 容器内的服务的方法。 它与 JUnit 集成,允许你编写一个测试类,在任何测试运行前就能启动容器。 Testcontainers 特别适合编写与真实后端服务(如 MySQL、MongoDB、Cassandra 等)通信的集成测试。spring-doc.cadn.net.cn

在接下来的章节中,我们将介绍一些你可以用来将测试容器与测试集成的方法。spring-doc.cadn.net.cn

使用春豆

Testcontainers 提供的容器可以被 Spring Boot 作为豆子管理。spring-doc.cadn.net.cn

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

import org.testcontainers.mongodb.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.mongodb.MongoDBContainer
import org.testcontainers.utility.DockerImageName

@TestConfiguration(proxyBeanMethods = false)
class MyTestConfiguration {

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

}

然后你可以通过导入测试类中的配置类来注入并使用容器:spring-doc.cadn.net.cn

import org.junit.jupiter.api.Test;
import org.testcontainers.mongodb.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.mongodb.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注释可用于纯版JUnit测试,或与@SpringBootTest:spring-doc.cadn.net.cn

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

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.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.neo4j.Neo4jContainer;

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

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

导入容器配置接口

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

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

import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.mongodb.MongoDBContainer;
import org.testcontainers.neo4j.Neo4jContainer;

interface MyContainers {

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

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

}
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.mongodb.MongoDBContainer
import org.testcontainers.neo4j.Neo4jContainer

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作为豆子管理时,它们的生命周期也由Spring管理:spring-doc.cadn.net.cn

这一过程确保任何依赖容器功能的豆子都能使用这些功能。 同时确保容器还在时清理干净。spring-doc.cadn.net.cn

当你的应用豆依赖容器的功能时,建议将容器配置为春豆,以确保生命周期行为正确。
由Testcontainers管理容器而非春季豆子,并不能保证豆子和容器关闭的顺序。 有时容器在咖啡豆尚未清理完之前就被关闭了。 这可能导致客户端豆子抛出异常,例如因连接中断而抛出异常。

容器豆节点由 Spring 的 TestContext 框架管理的每个应用上下文创建并启动一次。 有关TestContext框架如何管理底层应用上下文及其中的豆子的详细信息,请参阅Spring Framework文档spring-doc.cadn.net.cn

容器豆作为TestContext框架标准应用上下文关闭过程的一部分被停止。 当应用上下文关闭时,容器也会关闭。 这通常发生在所有使用该缓存应用上下文的测试完成后。 根据TestContext框架中配置的缓存行为,这种情况也可能更早发生。spring-doc.cadn.net.cn

单个测试容器实例可以且通常会在多个测试类的测试执行中被保留。

服务连接

服务连接是指连接到任何远程服务的连接。 Spring Boot 的自动配置可以消耗服务连接的详细信息,并利用这些细节建立与远程服务的连接。 在此过程中,连接细节优先于任何与连接相关的配置属性。spring-doc.cadn.net.cn

使用Testcontainers时,可以通过在测试类中标注容器字段,自动创建运行在容器中的服务的连接细节。spring-doc.cadn.net.cn

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

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.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.neo4j.Neo4jContainer;

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 相关豆子与运行在 Testcontainers 管理的 Docker 容器内的 Neo4j 通信。 这是通过自动定义Neo4jConnection详情然后 bean 被 Neo4j 自动配置使用,覆盖任何与连接相关的配置属性。spring-doc.cadn.net.cn

你需要添加Spring Boot测试容器模块作为测试依赖,以便与Testcontainers使用服务连接。

服务连接注释由以下方式处理ContainerConnectionDetailsFactory注册课程Spring。工厂. 一个ContainerConnectionDetailsFactory可以创建连接详情基于特定容器子类,或 Docker 镜像名称。spring-doc.cadn.net.cn

以下服务连接工厂位于Spring Boot测试容器罐:spring-doc.cadn.net.cn

连接详情 匹配内容

ActiveMQConnectionDetails(活跃MQConnectionDetails)spring-doc.cadn.net.cn

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

ArtemisConnection详情spring-doc.cadn.net.cn

类型的容器阿尔忒弥斯容器spring-doc.cadn.net.cn

Cassandra连接详情spring-doc.cadn.net.cn

类型的容器Cassandra容器spring-doc.cadn.net.cn

沙发底连接详情spring-doc.cadn.net.cn

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

ElasticsearchConnection详情spring-doc.cadn.net.cn

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

飞路连接详情spring-doc.cadn.net.cn

类型的容器Jdbc数据库容器spring-doc.cadn.net.cn

Jdbc连接详情spring-doc.cadn.net.cn

类型的容器Jdbc数据库容器spring-doc.cadn.net.cn

卡夫卡联系详情spring-doc.cadn.net.cn

类型的容器卡夫卡容器,ConfluentKafkaContainer红熊猫容器spring-doc.cadn.net.cn

LdapConnection详情spring-doc.cadn.net.cn

名为“osixia/openldap”或类型容器LLdap容器spring-doc.cadn.net.cn

LiquibaseConnection详情spring-doc.cadn.net.cn

类型的容器Jdbc数据库容器spring-doc.cadn.net.cn

MongoConnection详情spring-doc.cadn.net.cn

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

Neo4jConnection详情spring-doc.cadn.net.cn

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

OpenTelemetryLoggingConnectionDetails(开放遥测记录连接详情)spring-doc.cadn.net.cn

名为“otel/opentelemetry-collector-contrib”或类型的容器LgtmStackContainerspring-doc.cadn.net.cn

OtlpMetricsConnectionDetails(目标指标连接详情)spring-doc.cadn.net.cn

名为“otel/opentelemetry-collector-contrib”或类型的容器LgtmStackContainerspring-doc.cadn.net.cn

外部追踪连接详情spring-doc.cadn.net.cn

名为“otel/opentelemetry-collector-contrib”或类型的容器LgtmStackContainerspring-doc.cadn.net.cn

脉冲星连接详情spring-doc.cadn.net.cn

类型的容器脉冲星容器spring-doc.cadn.net.cn

R2dbc连接详情spring-doc.cadn.net.cn

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

兔子联系详情spring-doc.cadn.net.cn

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

RedisConnection详情spring-doc.cadn.net.cn

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

ZipkinConnection详情spring-doc.cadn.net.cn

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

默认情况下,所有适用的连接详情豆子都会为给定的某一节点创建容器. 例如,一个PostgreSQLContainer将两者皆有Jdbc连接详情R2dbc连接详情.spring-doc.cadn.net.cn

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

默认情况下Container.getDockerImageName().getRepository()用于获取连接信息所用的名称。 Docker 镜像名称中的仓库部分忽略了任何注册表和版本。 只要 Spring Boot 能够获取容器当使用静态的就像上面示例中的场。spring-doc.cadn.net.cn

如果你正在使用@BeanSpring Boot 不会调用 bean 方法获取 Docker 镜像名称,因为这会导致急于初始化的问题。 相反,使用豆子方法的返回类型来确定应使用哪种连接细节。 只要你使用类型化的容器,比如Neo4jContainerRabbitMQContainer. 如果你使用,这个方法就无法正常工作了通用容器例如,使用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靴分不清通用容器使用哪个容器图像,因此名称属性来自@ServiceConnection必须用来提供该提示。spring-doc.cadn.net.cn

你也可以使用名称属性@ServiceConnection用来覆盖将使用的连接细节,例如在使用自定义图像时。 如果你用的是 Docker 镜像registry.mycompany.com/mirror/myredis,你会用@ServiceConnection(name=“redis”)以确保RedisConnection详情被创造出来。spring-doc.cadn.net.cn

带服务连接的SSL系统

你可以使用@Ssl,@JksKeyStore,@JksTrustStore,@PemKeyStore@PemTrustStore在支持的容器上进行注释,以启用该服务连接的SSL支持。 请注意,你仍然需要在测试容器内运行的服务上启用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证书加载到truststore。 这将对客户端进行身份验证,信任存储中的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.data.elasticsearch.test.autoconfigure.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. 静电声@DynamicPropertySource方法允许向 Spring 环境添加动态属性值。spring-doc.cadn.net.cn

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

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.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.neo4j.Neo4jContainer

@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 相关豆子与运行在 Testcontainers 管理的 Docker 容器内的 Neo4j 通信。spring-doc.cadn.net.cn