TestContainers チートシート
インストール
前提条件
| 要件 | バージョン | 検証コマンド |
|---|
| Docker | 最新の安定版 | docker --version |
| Java JDK | 8以上 | java -version |
| Maven/Gradle | 最新 | mvn -v or gradle -v |
Maven 依存関係
<!-- Core TestContainers -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<!-- JUnit 5 Integration -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<!-- Database Modules -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
Gradle 依存関係
// Kotlin DSL
testImplementation("org.testcontainers:testcontainers:1.19.3")
testImplementation("org.testcontainers:junit-jupiter:1.19.3")
testImplementation("org.testcontainers:postgresql:1.19.3")
testImplementation("org.testcontainers:mysql:1.19.3")
testImplementation("org.testcontainers:kafka:1.19.3")
// Groovy DSL
testImplementation 'org.testcontainers:testcontainers:1.19.3'
testImplementation 'org.testcontainers:junit-jupiter:1.19.3'
testImplementation 'org.testcontainers:postgresql:1.19.3'
プラットフォーム別 Docker セットアップ
| プラットフォーム | インストールコマンド |
|---|
| Ubuntu/Debian | curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh |
| macOS | brew install --cask docker |
| Windows | Docker Desktopを docker.com からダウンロードする |
| Linux Post-Install | sudo usermod -aG docker $USER && newgrp docker |
基本コマンド
コンテナのライフサイクル操作
| コマンド | 説明 |
|---|
container.start() | コンテナインスタンスを起動する |
container.stop() | コンテナを停止して削除する |
container.isRunning() | コンテナが現在実行中かどうかを確認する |
container.getContainerId() | Docker コンテナ ID を取得する |
container.getDockerImageName() | 使用された画像名を取得 |
container.getLogs() | すべてのコンテナログを文字列として取得 |
container.getHost() | ホストアドレスを取得(通常はlocalhost) |
container.getMappedPort(int) | コンテナポートにマップされたホストポートを取得 |
container.getExposedPorts() | 公開されているコンテナポートをすべて一覧表示 |
container.getContainerInfo() | コンテナの詳細情報を取得 |
汎用コンテナ作成
| コマンド | 説明 |
|---|
new GenericContainer("image:tag") | 任意のDockerイメージからコンテナを作成 |
.withExposedPorts(port1, port2) | コンテナポートをホストに公開する |
.withEnv("KEY", "value") | 環境変数を設定する |
.withCommand("cmd", "arg1") | コンテナコマンドをオーバーライドする |
.withLabel("key", "value") | Dockerラベルをコンテナに追加する |
.withNetworkMode("host") | コンテナネットワークモードを設定 |
.withPrivilegedMode() | 特権モードでコンテナを実行 |
.withCreateContainerCmdModifier(cmd) | コンテナ作成コマンドを変更 |
.withStartupTimeout(Duration) | 起動待機の最大時間を設定 |
.withReuse(true) | テスト間でコンテナの再利用を有効にする |
データベースコンテナ
| コマンド | 説明 |
|---|
new PostgreSQLContainer("postgres:15") | PostgreSQLコンテナを作成する |
new MySQLContainer("mysql:8.0") | MySQL コンテナを作成する |
new MongoDBContainer("mongo:6.0") | MongoDB コンテナを作成する |
.withDatabaseName("dbname") | データベース名を設定 |
.withUsername("user") | データベースのユーザー名を設定 |
.withPassword("pass") | データベースパスワードを設定 |
.getJdbcUrl() | JDBC 接続 URL を取得 |
.getReplicaSetUrl() | MongoDB レプリカセットの URL を取得 (MongoDB) |
.withInitScript("init.sql") | 起動時にSQLスクリプトを実行 |
.withConfigurationOverride("path") | データベース設定を上書き |
高度な使用法
待機戦略
| コマンド | 説明 |
|---|
.waitingFor(Wait.forListeningPort()) | ポートがリスニング状態になるまで待機する |
.waitingFor(Wait.forHttp("/health")) | HTTPエンドポイントからの応答を待つ |
.waitingFor(Wait.forLogMessage(".*ready.*", 1)) | 特定のログメッセージを待つ |
.waitingFor(Wait.forHealthcheck()) | Docker ヘルスチェックが通過するまで待機する |
.waitingFor(Wait.forSuccessfulCommand("cmd")) | コマンドが成功するまで待機する |
.forStatusCode(200) | 予期される HTTP ステータスコードを指定する |
.forStatusCodeMatching(code -> code < 500) | カスタムステータスコードマッチャー |
.withStartupTimeout(Duration.ofMinutes(2)) | スタートアップタイムアウト時間を設定 |
.withReadTimeout(Duration.ofSeconds(10)) | HTTPの読み取りタイムアウトを設定 |
.forResponsePredicate(response -> true) | カスタムレスポンス検証 |
ファイルとボリュームの操作
| コマンド | 説明 |
|---|
.withFileSystemBind("host", "container") | ホストディレクトリをバインドマウント |
.withFileSystemBind(path, target, BindMode.READ_ONLY) | 特定のモードでマウント |
.withClasspathResourceMapping("res", "path") | マウントクラスパスリソース |
.copyFileToContainer(MountableFile, "path") | コンテナにファイルをコピーする |
.withCopyFileToContainer(file, path) | スタートアップ中にファイルをコピー |
.withTmpFs(Map.of("/tmp", "rw")) | tmpfsファイルシステムをマウント |
MountableFile.forClasspathResource("file") | クラスパスファイルを参照 |
MountableFile.forHostPath("/path") | ホスト・ファイルシステムのパスを参照 |
BindMode.READ_WRITE | 読み取りと書き込みアクセスを許可 |
BindMode.READ_ONLY | 読み取りアクセスのみを許可 |
ネットワーキング
| コマンド | 説明 |
|---|
Network.newNetwork() | カスタム Docker ネットワークを作成 |
.withNetwork(network) | コンテナをネットワークに接続する |
.withNetworkAliases("alias") | コンテナのネットワークエイリアスを設定 |
.dependsOn(otherContainer) | コンテナ依存関係を定義する |
.withExtraHost("hostname", "ip") | /etc/hostsにエントリーを追加 |
.getNetworkAliases() | すべてのネットワークエイリアスを取得 |
.withAccessToHost(true) | コンテナにホストへのアクセスを許可する |
Network.SHARED | テスト間で共有ネットワークを使用 |
.withNetworkMode("bridge") | 特定のネットワークモードを設定 |
.getNetwork() | ネットワークコンテナが接続されているものを取得する |
コンテナの実行
| コマンド | 説明 |
|---|
.execInContainer("cmd", "arg1") | 実行中のコンテナでコマンドを実行 |
.execInContainer(Charset, "cmd") | 特定の文字セットで実行 |
result.getStdout() | 実行からの標準出力を取得 |
result.getStderr() | 実行からの標準エラー取得 |
result.getExitCode() | コマンドの終了コードを取得 |
.copyFileFromContainer("path", consumer) | コンテナからファイルをコピーする |
.followOutput(consumer) | ストリームコンテナログ |
new Slf4jLogConsumer(logger) | SLF4Jロガーに出力をログ出力する |
new ToStringConsumer() | 出力を文字列として取得 |
new WaitingConsumer() | 特定の出力を待つ |
JUnit 5 統合
| コマンド | 説明 |
|---|
@Testcontainers | TestContainers 拡張機能を有効にする |
@Container | コンテナを管理するためにフィールドをマークする |
static @Container | すべてのテストでコンテナを共有 |
@DynamicPropertySource | コンテナプロパティを注入 |
registry.add("prop", container::getUrl) | 動的プロパティを追加 |
@BeforeAll | すべてのテストの前にコンテナをセットアップする |
@AfterAll | すべてのテストの後にコンテナをクリーンアップする |
@Nested | 共有コンテナを使用したグループテスト |
Startables.deepStart(containers).join() | 並列で複数のコンテナを起動する |
.withReuse(true) | テスト実行間でコンテナを再利用する |
設定
TestContainers プロパティ
作成testcontainers.propertiesをsrc/test/resourcesに:
# Docker host configuration
docker.host=unix:///var/run/docker.sock
# docker.host=tcp://localhost:2375
# Container reuse (requires Docker labels support)
testcontainers.reuse.enable=true
# Image pull policy
testcontainers.image.pull.policy=DefaultPullPolicy
# Ryuk container (cleanup)
testcontainers.ryuk.disabled=false
testcontainers.ryuk.container.image=testcontainers/ryuk:0.5.1
# Checks
testcontainers.checks.disable=false
Docker Compose 統合
@Container
static DockerComposeContainer environment =
new DockerComposeContainer(new File("docker-compose.yml"))
.withExposedService("db", 5432)
.withExposedService("redis", 6379)
.withLocalCompose(true)
.withPull(true)
.waitingFor("db", Wait.forHealthcheck());
カスタムコンテナ設定
public class CustomPostgreSQLContainer extends PostgreSQLContainer<CustomPostgreSQLContainer> {
private static final String IMAGE_VERSION = "postgres:15-alpine";
public CustomPostgreSQLContainer() {
super(IMAGE_VERSION);
this.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass")
.withInitScript("init.sql")
.withCommand("postgres -c max_connections=200");
}
}
Spring Boot 統合
@SpringBootTest
@Testcontainers
class ApplicationTests {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
.withDatabaseName("testdb");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.jpa.hibernate.ddl-auto", () -> "create-drop");
}
@Test
void contextLoads() {
// Test with real database
}
}
一般的なユースケース
ユースケース 1: PostgreSQL 統合テスト
Note: Some sections were left blank as no specific text was provided to translate. If you have the specific text for those sections, I can help you translate them.```java
@Testcontainers
@SpringBootTest
class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test")
.withInitScript("schema.sql");
@DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private UserRepository userRepository;
@Test
void shouldSaveAndFindUser() {
User user = new User("john@example.com", "John Doe");
userRepository.save(user);
Optional<User> found = userRepository.findByEmail("john@example.com");
assertTrue(found.isPresent());
assertEquals("John Doe", found.get().getName());
}
}
```java
@Testcontainers
class KafkaIntegrationTest {
@Container
static KafkaContainer kafka = new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.5.0")
);
@DynamicPropertySource
static void kafkaProperties(DynamicPropertyRegistry registry) {
registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
}
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@Autowired
private MessageListener messageListener;
@Test
void shouldProcessMessage() throws Exception {
String topic = "test-topic";
String message = "Hello Kafka!";
kafkaTemplate.send(topic, message);
// Wait for message processing
await().atMost(Duration.ofSeconds(10))
.until(() -> messageListener.getReceivedMessages().size() == 1);
assertEquals(message, messageListener.getReceivedMessages().get(0));
}
}
```### ユースケース3: マルチコンテナマイクロサービステスト
```java
@Testcontainers
class MicroservicesIntegrationTest {
static Network network = Network.newNetwork();
@Container
static PostgreSQLContainer<?> database = new PostgreSQLContainer<>("postgres:15-alpine")
.withNetwork(network)
.withNetworkAliases("database")
.withDatabaseName("orders");
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withNetwork(network)
.withNetworkAliases("redis")
.withExposedPorts(6379);
@Container
static GenericContainer<?> orderService = new GenericContainer<>("order-service:latest")
.withNetwork(network)
.withExposedPorts(8080)
.withEnv("DATABASE_URL", "jdbc:postgresql://database:5432/orders")
.withEnv("REDIS_HOST", "redis")
.dependsOn(database, redis)
.waitingFor(Wait.forHttp("/actuator/health").forStatusCode(200));
@Test
void shouldCreateOrder() {
String baseUrl = "http://" + orderService.getHost() + ":"
+ orderService.getMappedPort(8080);
// Make HTTP request to order service
RestTemplate restTemplate = new RestTemplate();
Order order = new Order("item-123", 2);
ResponseEntity<Order> response = restTemplate.postForEntity(
baseUrl + "/orders", order, Order.class
);
assertEquals(HttpStatus.CREATED, response.getStatusCode());
assertNotNull(response.getBody().getId());
}
}
```### ユースケース4: MongoDB統合テスト
```java
@Testcontainers
@DataMongoTest
class ProductRepositoryTest {
@Container
static MongoDBContainer mongodb = new MongoDBContainer("mongo:6.0")
.withExposedPorts(27017);
@DynamicPropertySource
static void mongoProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", mongodb::getReplicaSetUrl);
}
@Autowired
private ProductRepository productRepository;
@Test
void shouldFindProductsByCategory() {
// Insert test data
productRepository.save(new Product("Laptop", "Electronics", 999.99));
productRepository.save(new Product("Mouse", "Electronics", 29.99));
productRepository.save(new Product("Desk", "Furniture", 299.99));
// Query by category
List<Product> electronics = productRepository.findByCategory("Electronics");
assertEquals(2, electronics.size());
assertTrue(electronics.stream()
.allMatch(p -> p.getCategory().equals("Electronics")));
}
}
```### ユースケース5: Docker Composeテスト環境
```java
@Testcontainers
class FullStackIntegrationTest {
@Container
static DockerComposeContainer<?> environment = new DockerComposeContainer<>(
new File("src/test/resources/docker-compose-test.yml")
)
.withExposedService("api", 8080,
Wait.forHttp("/health").forStatusCode(200))
.withExposedService("postgres", 5432,
Wait.forListeningPort())
.withExposedService("redis", 6379)
.withLocalCompose(true);
@Test
void shouldConnectToAllServices() {
String apiHost = environment.getServiceHost("api", 8080);
Integer apiPort = environment.getServicePort("api", 8080);
String apiUrl = "http://" + apiHost + ":" + apiPort;
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity(
apiUrl + "/health", String.class
);
assertEquals(HttpStatus.OK, response.getStatusCode());
}
}
```## ベストプラクティス
### コンテナ管理
- **テストクラス用の静的コンテナを使用**: すべてのテストメソッド間でコンテナを共有し、起動時間とリソース使用量を削減する
- **コンテナ再利用を実装**: `testcontainers.reuse.enable=true`を有効にし、ローカル開発テストサイクルを高速化する
- **リソースをクリーンアップ**: JUnit統合外でコンテナを手動管理する際は、`@AfterAll`または try-with-resources を使用する
- **特定のイメージタグを使用**: `latest`タグを避け、`postgres:15-alpine`のような具体的なバージョンを使用して再現可能なテストを行う
### パフォーマンス最適化
- **並列コンテナ起動**: `Startables.deepStart()`を使用して、複数の独立したコンテナを同時に起動する
- **コンテナ再起動を最小限に**: 可能な限り、メソッドレベルではなくクラスレベルの`@Container`を使用する
- **軽量なイメージを選択**: Alpine ベースのイメージ(例:`postgres:15-alpine`)を優先し、プル時間とディスク使用量を削減する
- **待機戦略を賢明に実装**: 不必要な遅延を避けながら、コンテナの準備を確実にするための適切な待機戦略を使用する
### テスト戦略
- **本番に近い依存関係でテスト**: 組み込みバージョンではなく、実際のデータベースエンジンやメッセージブローカーを使用する
- **テストデータを分離**: 各テストが独自のデータを作成し、クリーンアップすることで、テスト間の依存関係を防ぐ
- **ネットワークエイリアスを使用**: カスタムネットワークを作成し、コンテナ間通信に意味のあるエイリアスを使用する
- **ヘルスチェックを実装**: テスト実行前にコンテナが完全に準備できるよう、常に適切な待機戦略を設定する
### CI/CD統合
- **Dockerの可用性を確認**: CIの環境にDockerがインストールされ、アクセス可能であることを確認する
- **タイムアウトを適切に設定**: CIの環境のパフォーマンスを考慮して、現実的な起動タイムアウトを設定する
- **イメージキャッシングを使用**: CIがDockerイメージをキャッシュし、後続の実行を高速化するよう設定する
- **リソース使用量を監視**: CIの環境におけるメモリとCPUの制限に注意し、コンテナ設定を適宜調整する
### セキュリティとメンテナンス
- **TestContainersを最新に保つ**: セキュリティパッチと新機能のため、最新バージョンに定期的に更新する
- **公式イメージを使用**: 検証済みの発行元の公式Dockerイメージを優先する
- **特権モードを避ける**: 絶対に必要な場合のみ`.withPrivilegedMode()`を使用する
- **公開ポートを確認**: テストに実際に必要なポートのみを公開する
## トラブルシューティング
| 問題 | ソリューション |
|-------|----------|
| **Container fails to start: "Cannot connect to Docker daemon"** | Ensure Docker is running: `docker ps`. On Linux, add user to docker group: `sudo usermod -aG docker $USER` |
| **Tests timeout during container startup** | Increase timeout: `.withStartupTimeout(Duration.ofMinutes(5))` or check Docker resources (CPU/Memory) |
| **Port binding conflicts: "Address already in use"** | Use dynamic ports: `container.getMappedPort()` instead of fixed ports, or stop conflicting services |
| **Image pull fails: "manifest unknown"** | Verify image name and tag exist on Docker Hub. Use specific tags instead of `latest` |
| **Container starts but tests fail to connect** | Check wait strategy is appropriate: add `.waitingFor(Wait.forListeningPort())` or `.waitingFor(Wait.forHealthcheck())` |
| **Out of memory errors during tests** | Reduce number of parallel containers, increase Docker memory limit in Docker Desktop settings, or use `.withReuse(true)` |
| **"Ryuk container not found" warnings** | Disable Ryuk if needed: set `testcontainers.ryuk.disabled=true` in properties (not recommended for production) |
| **Tests work locally but fail in CI** | CIにDockerがインストールされていることを確認し、タイムアウト設定を確認し、ネットワーク接続を検証し、CIの環境リソース制限を確認する |
| **Database connection refused** | Wait for database to be ready: use `.waitingFor(Wait.forListeningPort())` and verify JDBC URL with `container.getJdbcUrl()` |
| **Container logs show errors but tests pass** | Enable log following: `container.followOutput(new Slf4jLogConsumer(log))` to debug container issues |
| **Slow test execution** | Use container reuse, share containers at class level, start containers in parallel with `Startables.deepStart()` |
| **"No such file or directory" when mounting volumes** | Use absolute paths or `MountableFile.forClasspathResource()` for classpath resources |
| **Network communication between containers fails** | Create custom network: `Network.newNetwork()` and attach containers with `.withNetwork(network)` and `.withNetworkAliases()` |
| **TestContainers properties not being read** | Ensure `testcontainers.properties` is in `src/test/resources` and properly formatted |
| **Container cleanup not happening** | Check Ryuk container is running: `docker ps | grep ryuk`. Ensure tests complete normally without hanging |
## 利用可能なモジュール
| モジュール | Maven アーティファクト | 説明 |
|--------|---------------|-------------|
| PostgreSQL | `testcontainers-postgresql` | PostgreSQL データベースコンテナ |
| MySQL | `testcontainers-mysql` | MySQLデータベースコンテナ |
| MongoDB | `testcontainers-mongodb` | MongoDB ドキュメントデータベース |
| Kafka | `testcontainers-kafka` | Apache Kafkaメッセージブローカー |
| Redis | `testcontainers-redis` | Redis インメモリデータストア |
| Elasticsearch | `testcontainers-elasticsearch` | Elasticsearch 検索エンジン |
| Cassandra | `testcontainers-cassandra` | Cassandra NoSQL データベース |
| RabbitMQ | `testcontainers-rabbitmq` | RabbitMQ メッセージブローカー |
| Selenium | `testcontainers-selenium` | ブラウザ自動化コンテナ |
| LocalStack | `testcontainers-localstack` | AWS サービスのエミュレーション || Nginx