Testcontainers

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

Testcontainers 可按如下方式用于 Spring Boot 测试

  • Java

  • Kotlin

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;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

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

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

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

服务连接

服务连接是与任何远程服务的连接。Spring Boot 的自动配置可以消费服务连接的详细信息,并使用它们来建立与远程服务的连接。在此过程中,连接详细信息优先于任何连接相关的配置属性。

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

  • Java

  • Kotlin

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 进行通信。这是通过自动定义一个 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 类型的容器

CouchbaseConnectionDetails

CouchbaseContainer 类型的容器

ElasticsearchConnectionDetails

ElasticsearchContainer 类型的容器

FlywayConnectionDetails

JdbcDatabaseContainer 类型的容器

JdbcConnectionDetails

JdbcDatabaseContainer 类型的容器

KafkaConnectionDetails

KafkaContainerConfluentKafkaContainerRedpandaContainer 类型的容器

LiquibaseConnectionDetails

JdbcDatabaseContainer 类型的容器

MongoConnectionDetails

MongoDBContainer 类型的容器

Neo4jConnectionDetails

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" 的容器

默认情况下,将为给定的 Container 创建所有适用的连接详细信息 Bean。例如,PostgreSQLContainer 将创建 JdbcConnectionDetailsR2dbcConnectionDetails

如果你只想创建适用类型的一个子集,可以使用 @ServiceConnectiontype 属性。

默认情况下,使用 Container.getDockerImageName().getRepository() 获取用于查找连接详细信息的名称。Docker 镜像名称的 repository 部分会忽略任何 registry 和版本。只要 Spring Boot 能够获取 Container 的实例,就像上面示例中使用的 static 字段一样,这种方式就有效。

如果你使用 @Bean 方法,Spring Boot 不会调用 bean 方法来获取 Docker 镜像名称,因为这会导致早期初始化问题。相反,会使用 bean 方法的返回类型来确定应使用哪种连接详细信息。只要你使用像 Neo4jContainerRabbitMQContainer 这样的类型化容器,这种方式就有效。如果你使用 GenericContainer,例如下面的 Redis 示例所示,这种方式就无效了

  • Java

  • Kotlin

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 判断使用了哪个容器镜像,因此必须使用 @ServiceConnectionname 属性来提供该提示。

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

动态属性

@DynamicPropertySource 是服务连接的一种稍微更冗长但也更灵活的替代方案。静态 @DynamicPropertySource 方法允许向 Spring Environment 添加动态属性值。

  • Java

  • Kotlin

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 进行通信。