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 進行通訊。