Testcontainers

Testcontainers (英語) ライブラリは、Docker コンテナー内で実行されるサービスを管理する方法を提供します。JUnit と統合されているため、テストを実行する前にコンテナーを起動できるテストクラスを作成できます。Testcontainers は、MySQL、MongoDB、Cassandra などの実際のバックエンドサービスと通信する統合テストを作成する場合に特に役立ちます。

テストコンテナーは、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 と通信できます。これは、Neo4j 自動構成によって使用される Neo4jConnectionDetails Bean を自動的に定義し、接続関連の構成プロパティをオーバーライドすることによって行われます。

Testcontainers とのサービス接続を使用するには、spring-boot-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.KafkaContainer または RedpandaContainer 型のコンテナー

LiquibaseConnectionDetails

JdbcDatabaseContainer 型のコンテナー

MongoConnectionDetails

MongoDBContainer 型のコンテナー

Neo4jConnectionDetails

Neo4jContainer 型のコンテナー

OtlpMetricsConnectionDetails

"otel/opentelemetry-collector-contrib" という名前のコンテナー

OtlpTracingConnectionDetails

"otel/opentelemetry-collector-contrib" という名前のコンテナー

PulsarConnectionDetails

PulsarContainer 型のコンテナー

R2dbcConnectionDetails

MariaDBContainerMSSQLServerContainerMySQLContainerOracleContainer または PostgreSQLContainer 型のコンテナー

RabbitConnectionDetails

RabbitMQContainer 型のコンテナー

RedisConnectionDetails

"redis" という名前のコンテナー

ZipkinConnectionDetails

"openzipkin/zipkin" という名前のコンテナー

デフォルトでは、該当するすべての接続詳細 Bean が特定の Container に対して作成されます。例: PostgreSQLContainer は JdbcConnectionDetails と R2dbcConnectionDetails の両方を作成します。

該当する型のサブセットのみを作成する場合は、@ServiceConnection の type 属性を使用できます。

デフォルトでは、接続の詳細を見つけるために使用される名前を取得するために Container.getDockerImageName().getRepository() が使用されます。Docker イメージ名のリポジトリ部分は、レジストリとバージョンを無視します。これは、Spring Boot が Container のインスタンスを取得できる限り機能します。これは、上記の例のように static フィールドを使用する場合に当てはまります。

@Bean メソッドを使用している場合、Spring Boot は Bean メソッドを呼び出して Docker イメージ名を取得しません。これは、早期初期化の問題が発生する可能性があるためです。代わりに、Bean メソッドの戻り値の型を使用して、どの接続詳細を使用する必要があるかを調べます。これは、型付きコンテナーを使用している限り機能します。Neo4jContainer または RabbitMQContainerGenericContainer を使用している場合、これは機能しなくなります。次の例に示すように、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 属性を使用する必要があります。

@ServiceConnection の name 属性を使用して、カスタムイメージを使用する場合などに、どの接続詳細が使用されるかをオーバーライドすることもできます。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 関連の Bean が、Testcontainers が管理する Docker コンテナー内で実行されている Neo4j と通信できるようになります。