Testcontainers

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 相關的 beans 與在 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

org.testcontainers.containers.KafkaContainerRedpandaContainer 類型的容器

LiquibaseConnectionDetails

JdbcDatabaseContainer 類型的容器

MongoConnectionDetails

MongoDBContainer 類型的容器

Neo4jConnectionDetails

Neo4jContainer 類型的容器

OtlpMetricsConnectionDetails

名為 "otel/opentelemetry-collector-contrib" 的容器

OtlpTracingConnectionDetails

名為 "otel/opentelemetry-collector-contrib" 的容器

PulsarConnectionDetails

PulsarContainer 類型的容器

R2dbcConnectionDetails

MariaDBContainer, MSSQLServerContainer, MySQLContainer, OracleContainer, 或 PostgreSQLContainer 類型的容器

RabbitConnectionDetails

RabbitMQContainer 類型的容器

RedisConnectionDetails

名為 "redis" 的容器

ZipkinConnectionDetails

名為 "openzipkin/zipkin" 的容器

預設情況下,將為給定的 Container 建立所有適用的連線詳細資訊 beans。例如,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 中判斷使用了哪個容器映像檔,因此必須使用 @ServiceConnection 中的 name 屬性來提供該提示。

您也可以使用 @ServiceConnectionname 屬性來覆蓋將使用哪個連線詳細資訊,例如當使用自訂映像檔時。如果您正在使用 Docker 映像檔 registry.mycompany.com/mirror/myredis,您可以使用 @ServiceConnection(name="redis") 來確保建立 RedisConnectionDetails

動態屬性

@DynamicPropertySource 是一種比服務連線稍微冗長但更靈活的替代方案。靜態 @DynamicPropertySource 方法允許將動態屬性值新增到 Spring 環境。

  • 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 相關的 beans 與在 Testcontainers 管理的 Docker 容器內運行的 Neo4j 通訊。