コンテンツにスキップ

TestContainers チートシート

TestContainers チートシート

インストール

前提条件

要件バージョン検証コマンド
Docker最新の安定版docker --version
Java JDK8以上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/Debiancurl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
macOSbrew install --cask docker
WindowsDocker Desktopを docker.com からダウンロードする
Linux Post-Installsudo 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 統合

コマンド説明
@TestcontainersTestContainers 拡張機能を有効にする
@Containerコンテナを管理するためにフィールドをマークする
static @Containerすべてのテストでコンテナを共有
@DynamicPropertySourceコンテナプロパティを注入
registry.add("prop", container::getUrl)動的プロパティを追加
@BeforeAllすべてのテストの前にコンテナをセットアップする
@AfterAllすべてのテストの後にコンテナをクリーンアップする
@Nested共有コンテナを使用したグループテスト
Startables.deepStart(containers).join()並列で複数のコンテナを起動する
.withReuse(true)テスト実行間でコンテナを再利用する

設定

TestContainers プロパティ

作成testcontainers.propertiessrc/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