Testcontainers は、テストの開始時に Docker で本物の PostgreSQL などを起動し、終わったら片付けてくれる Java のライブラリです。 H2 のような代わりの DB ではなく、本番と同じ DB でテストできます。Spring Boot では、コンテナに @ServiceConnection を付けるだけで、spring.datasource.url などの接続先が自動で設定されます。

@SpringBootTest
@Testcontainers
class UserRepositoryTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:17-alpine");

    @Autowired UserRepository repository;

    @Test
    void save() { … }
}
書き方 接続先の設定 向いている場面
@Container + @ServiceConnection Boot が自動で設定 ふつうはこれ。PostgreSQL・MySQL・Redis など対応のもの
@Container + @DynamicPropertySource 自分で registry.add(…) 対応していないサービス、独自の設定名を使う
@Bean + @ServiceConnection(@TestConfiguration) Boot が自動で設定 複数のテストクラスで共有。開発時の起動にも使える

確認環境:Testcontainers 2.0.5(GitHub のタグで最新の版。Spring Boot 4.1.1 が管理する版と同じ)と Spring Boot 4.1.1。依存の名前・クラス名・既定値・エラー文は、Testcontainers のタグ 2.0.5 のソースとドキュメント、2.0.0 のリリースノート、Spring Boot のタグ v4.1.1 のソース(spring-boot-testcontainers・AutoConfigureTestDatabase)とドキュメントで確かめました。GitHub Actions の Ubuntu ランナーに Docker が入っていることは、GitHub の runner-images の一覧で確かめました(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Testcontainersの使い方|Spring BootでPostgreSQL」

Testcontainers 2.0 で変わったこと

2.0 では、依存の名前とパッケージが変わりました。 1.x の記事のコードをそのまま写すと、依存が見つからないかクラスが非推奨になります。

1.x 2.0
PostgreSQL の依存 org.testcontainers:postgresql org.testcontainers:testcontainers-postgresql
JUnit 5 の依存 org.testcontainers:junit-jupiter org.testcontainers:testcontainers-junit-jupiter
PostgreSQL のクラス org.testcontainers.containers.PostgreSQLContainer<?> org.testcontainers.postgresql.PostgreSQLContainer(型引数なし)
旧クラス — 残っているが @Deprecated
引数なしのコンストラクタ あった(既定の版を使う) 削除。イメージ名を必ず書く
JUnit 4(@Rule) 対応 削除

リリースノートには「すべてのモジュールに testcontainers- の接頭辞が付く」「コンテナのクラスは org.testcontainers.<モジュール名> に移る」「JUnit 4 のサポートを削除」とあります。PostgreSQL のコンテナの既定のユーザー名・パスワード・DB 名は、ソースではどれも test です。

依存(Spring Boot 4.1.1)

版は Spring Boot が管理する(Testcontainers の BOM を読み込む)ので、書きません。

build.gradle.kts

plugins {
    java
    id("org.springframework.boot") version "4.1.1"
    id("io.spring.dependency-management") version "1.1.7"
}

java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }

repositories { mavenCentral() }

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webmvc")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-flyway")
    implementation("org.flywaydb:flyway-database-postgresql")
    runtimeOnly("org.postgresql:postgresql")

    testImplementation("org.springframework.boot:spring-boot-starter-data-jpa-test")
    testImplementation("org.springframework.boot:spring-boot-testcontainers")       // @ServiceConnection
    testImplementation("org.testcontainers:testcontainers-junit-jupiter")           // @Testcontainers・@Container
    testImplementation("org.testcontainers:testcontainers-postgresql")              // PostgreSQLContainer
}

tasks.test { useJUnitPlatform() }

pom.xml(dependencies の部分)

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-testcontainers</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>testcontainers-junit-jupiter</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>testcontainers-postgresql</artifactId>
  <scope>test</scope>
</dependency>
依存 何のため
spring-boot-testcontainers @ServiceConnection と、コンテナを Bean として扱うしくみ(Boot のドキュメントに「service connection を使うには test の依存に足す」とある)
testcontainers-junit-jupiter JUnit 5 の拡張(@Testcontainers・@Container)
testcontainers-postgresql PostgreSQLContainer

Boot 4 のテスト用 starter(spring-boot-starter-data-jpa-test など)は、関連記事「Spring Boot starter一覧|用途別の選び方とBoot 4の新旧対応表」で、Gradle の書き方は、関連記事「build.gradle書き方|Groovy/Kotlin DSL対訳と依存設定一覧」で扱っています。

書き方①:@Testcontainers + @Container + @ServiceConnection

テストの対象です。

// src/main/java/com/example/demo/User.java
@Entity
@Table(name = "users")
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
    protected User() {}
    public User(String name, String email) { this.name = name; this.email = email; }
    public Long getId() { return id; }
    public String getName() { return name; }
    public String getEmail() { return email; }
}

// src/main/java/com/example/demo/UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
    @Query(value = "SELECT * FROM users WHERE name ILIKE CONCAT('%', :keyword, '%')", nativeQuery = true)
    List<User> searchByName(@Param("keyword") String keyword);   // PostgreSQL の ILIKE
}

テーブルは Flyway で作ります(src/main/resources/db/migration/V1__create_users.sql)。Flyway の使い方は、関連記事「Flywayの使い方|Spring Boot・命名規則・失敗時の直し方」で扱っています。

// src/test/java/com/example/demo/UserRepositoryTest.java
package com.example.demo;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.postgresql.PostgreSQLContainer;

@DataJpaTest
@Testcontainers
class UserRepositoryTest {

    @Container          // テストクラスの前に起動、後に停止(static なのでクラス内で共有)
    @ServiceConnection  // 接続先を Boot に教える
    static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:17-alpine");

    @Autowired
    UserRepository repository;

    @Test
    void ILIKEで大文字小文字を区別せずに探せる() {
        repository.save(new User("Taro Yamada", "taro@example.com"));
        repository.save(new User("Hanako", "hanako@example.com"));

        assertThat(repository.searchByName("taro"))
                .extracting(User::getName)
                .containsExactly("Taro Yamada");
    }
}

@DataJpaTest は、ふつうは DB を組み込みの DB(H2 など)に差し替えます。 ただし Boot 4.1.1 の @AutoConfigureTestDatabase の既定は Replace.NON_TEST で、ソースのコメントには「@ServiceConnection を付けた Testcontainers の DB」「@DynamicPropertySource で設定した spring.datasource.url」はテスト用の DB とみなして差し替えないとあります。そのため、@AutoConfigureTestDatabase(replace = NONE) は書かなくても PostgreSQL に繋がります。

ILIKE は PostgreSQL の書き方です。 このように「本番の DB でしか動かない SQL」を確かめられるのが、Testcontainers を使う一番の理由です。AssertJ の extracting は、関連記事「AssertJの使い方|assertThatの早見表(文字列・リスト・例外・日付)」で扱っています。

@Container を static にするかどうか

Testcontainers のドキュメントでは、次のように説明されています。

フィールド 起動と停止 速さ
static クラスの最初のテストの前に1回起動、最後のテストの後に停止 速い(クラスで共有)
static でない テストメソッドごとに起動と停止 遅い

@ServiceConnection を付けるフィールドは static にします。 Boot のソースでは、static でないと次のエラーになります。

@ServiceConnection field 'postgres' must be static

また、Testcontainers のドキュメントには、この拡張は「テストを順番に実行する前提で確かめていて、並列実行では動きが保証されない」とあります。

書き方②:@DynamicPropertySource(自分で設定する)

@ServiceConnection が使えないとき(対応していないサービス、独自の設定名など)は、@DynamicPropertySource で設定を足します。

@SpringBootTest
@Testcontainers
class UserApiTest {

    @Container
    static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:17-alpine")
            .withDatabaseName("app")
            .withUsername("app")
            .withPassword("secret");

    @DynamicPropertySource
    static void props(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("app.feature.search-enabled", () -> "true");   // 独自の設定も足せる
    }

    // テスト …
}

@ServiceConnection と @DynamicPropertySource の違い

@ServiceConnection @DynamicPropertySource
書く量 注釈1つ 設定名を1つずつ registry.add
何を設定するか JdbcConnectionDetails などの Bean を作る Spring の Environment に設定値を足す
優先 Boot のドキュメントでは「接続に関する設定値より優先」 ふつうの設定値と同じ並びで効く
対応しているもの Boot に用意があるもの(PostgreSQL・MySQL・MariaDB・Oracle・SQL Server・MongoDB・Redis・Kafka・RabbitMQ・Elasticsearch など) 何でも
@DataJpaTest で差し替えられない される(テスト用 DB とみなす) spring.datasource.url を足した場合はされる
メソッド — static にする

Boot のドキュメントでは、@DynamicPropertySource は「service connection より少し冗長だが、より柔軟な方法」と書かれています。PostgreSQLContainer に @ServiceConnection を付けると、JDBC 用(JdbcConnectionDetails)と R2DBC 用(R2dbcConnectionDetails)の両方が作られます。片方だけにしたいときは、@ServiceConnection(type = JdbcConnectionDetails.class) のように type を指定します。

書き方③:コンテナを Bean にして共有する

Boot のドキュメントは、テストクラスをまたいで同じコンテナを使う場合、コンテナを Spring の Bean にする書き方をすすめています。 JUnit の拡張で管理すると、クラスの終わりにコンテナが止まりますが、Spring はテスト間でアプリケーションコンテキスト(Bean の入れ物)をキャッシュして使い回すため、止まったコンテナを参照して失敗することがあるからです。

// src/test/java/com/example/demo/TestcontainersConfiguration.java
package com.example.demo;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.postgresql.PostgreSQLContainer;

@TestConfiguration(proxyBeanMethods = false)
public class TestcontainersConfiguration {

    @Bean
    @ServiceConnection
    PostgreSQLContainer postgresContainer() {
        return new PostgreSQLContainer("postgres:17-alpine");
    }
}
@SpringBootTest
@Import(TestcontainersConfiguration.class)
class OrderServiceTest {
    // @Testcontainers も @Container も要らない
}

Boot のドキュメントによると、Bean にしたコンテナは「ほかの Bean より先に起動し、ほかの Bean を片付けた後に停止」し、同じアプリケーションコンテキストを使うテストの間で1つのコンテナが使い回されます。

開発時のアプリ起動にも使う

同じ設定で、ローカルでアプリを起動するときにも PostgreSQL を立てられます。src/test/java に起動用のクラスを置きます。

// src/test/java/com/example/demo/TestDemoApplication.java
package com.example.demo;

import org.springframework.boot.SpringApplication;

public class TestDemoApplication {
    public static void main(String[] args) {
        SpringApplication.from(DemoApplication::main)
                .with(TestcontainersConfiguration.class)
                .run(args);
    }
}

Boot のドキュメントでは、Gradle の bootTestRun、Maven の spring-boot:test-run で起動できるとあります。Docker Compose で開発用の DB を立てる方法は、関連記事「Docker Composeの使い方|compose.yamlの書き方と開発環境の作り方」で扱っています。

コンテナの再利用(reuse)

テストを実行するたびにコンテナを起動すると、数秒かかります。 ローカルで何度も実行するときは、コンテナを止めずに使い回す「reuse」という機能があります。ドキュメントでは Experimental(試験的) と書かれています。

static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:17-alpine")
        .withReuse(true);

static {
    postgres.start();    // 自分で start() する。stop() は呼ばない
}
# ~/.testcontainers.properties(ホームディレクトリ)
testcontainers.reuse.enable=true
条件 内容(Testcontainers のドキュメント)
有効にする場所 環境変数 TESTCONTAINERS_REUSE_ENABLE=true か、~/.testcontainers.properties
有効にできない場所 クラスパス上の properties ファイル(src/test/resources など)
起動のしかた start() を自分で呼ぶ。@Container や try-with-resources で止めない
再利用される条件 コンテナの設定がまったく同じ
CI 向いていない(ドキュメントに明記)

withReuse(true) を書いても、環境で有効にしていないと、次の警告が出て、ふつうのコンテナとして動きます(ソースの文言)。

Reuse was requested but the environment does not support the reuse of containers
To enable reuse of containers, you must set 'testcontainers.reuse.enable=true' in a file located at /home/<user>/.testcontainers.properties

再利用したコンテナには前回のデータが残ります。 テストごとにデータを消す(@Transactional でロールバック、@Sql で TRUNCATE など)ようにします。@DataJpaTest は、テストごとにトランザクションをロールバックします。

GitHub Actions で実行する

GitHub が用意している Ubuntu のランナー(ubuntu-latest)には Docker が入っているので、追加の設定なしで Testcontainers が動きます。 GitHub の runner-images の一覧(Ubuntu 24.04)には、Docker Client・Docker Server 28.0.4、Docker Compose 2.38.2 が載っています。

# .github/workflows/test.yml
name: test

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '21'

      - uses: gradle/actions/setup-gradle@v4

      - name: Test
        run: ./gradlew test

      - name: Upload test report
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: test-report
          path: build/reports/tests/test

Maven なら run: ./mvnw -B verify にします。

注意点 内容
ランナーの OS Linux のランナーを使う(Docker が動く)
イメージの取得回数 Docker Hub は取得回数に上限がある。Testcontainers のドキュメントにも「image registry rate limiting」の章がある
withReuse(true) CI では使わない(ドキュメントで非推奨)
時間 イメージの取得とコンテナの起動の分だけ、テストが長くなる

GitHub Actions の基本は、関連記事「CI/CDとは|GitHub Actionsで最初のパイプラインを作るまで」で、キャッシュの設定は、関連記事「GitHub Actionsのキャッシュとmatrix|コピペ用YAMLとキー設計」で扱っています。

エラー文と原因の表

エラー文は、Testcontainers 2.0.5 と Spring Boot 4.1.1 のソースにある文言です。

エラー文(原文) 原因 対処
Could not find a valid Docker environment. Please see logs and check configuration Docker が起動していない、ソケットに接続できない Docker を起動する。ログの「Attempted configurations were:」で試した接続先を見る
Previous attempts to find a Docker environment failed. Will not retry. Please see logs and check configuration 上のエラーの後、同じ JVM の2回目以降のテスト 最初のエラーを直す
Container startup failed for image postgres:17-alpine コンテナの起動に失敗(イメージが取れない・すぐ落ちた) 原因(cause)の例外を見る
Could not create/start container 上と同じく起動の失敗 Docker のログ、ディスクの空き、メモリ
Timed out waiting for container port to open (…) ポートが開くのを待つ間に時間切れ Docker の負荷、起動待ちの時間
Wait strategy failed. Container exited with code … 起動待ちの間にコンテナが終了 環境変数や設定の誤り。コンテナのログを見る
Wait strategy failed. Container crashed with out-of-memory (OOMKilled) メモリ不足で強制終了 Docker に割り当てるメモリを増やす
Failed to verify that image '…' is a compatible substitute for '…'. postgres 以外の名前のイメージ(社内ミラーなど)を指定 DockerImageName.parse("…").asCompatibleSubstituteFor("postgres")
@ServiceConnection field 'postgres' must be static @ServiceConnection のフィールドが static でない static を付ける
No ConnectionDetails found for source '…'. You may need to add a 'name' to your @ServiceConnection annotation GenericContainer などで、Boot が何のサービスか分からない @ServiceConnection(name = "redis") のように名前を書く
Reuse was requested but the environment does not support the reuse of containers withReuse(true) だが環境で有効にしていない ~/.testcontainers.properties に testcontainers.reuse.enable=true

Docker が無い環境(一部の CI や、Docker を入れていない PC)でテストを飛ばしたいときは、@Testcontainers(disabledWithoutDocker = true) にします。 ソースでは、Docker が無いと disabledWithoutDocker is true and Docker is not available という理由でテストが「無効(disabled)」になり、失敗ではなくスキップとして扱われます。

Docker の接続そのもののエラーは、関連記事「Cannot connect to the Docker daemonの原因と直し方」で扱っています。

Docker Desktop 以外を使うとき

Testcontainers のドキュメントでは、対応する実行環境として Docker Desktop、Linux の Docker Engine、Testcontainers Cloud が挙がっています。それ以外(Colima・Podman・リモートの Docker)は、接続先の設定が要ることがあります。

設定 意味
DOCKER_HOST(環境変数) Docker の接続先。既定は unix:///var/run/docker.sock
docker.host(~/.testcontainers.properties) 上と同じ
TESTCONTAINERS_HOST_OVERRIDE(環境変数) コンテナに繋ぐときのホスト名を上書き
TESTCONTAINERS_RYUK_DISABLED=true 後片付け用のコンテナ(Ryuk)を使わない。特権コンテナが使えない環境向け

よくある質問

Q. H2 でテストすればよいのでは? A. H2 は PostgreSQL と SQL の方言や型が違うため、ILIKE・ON CONFLICT・jsonb などの PostgreSQL の機能は確かめられません。Testcontainers なら、本番と同じ版の PostgreSQL で確かめられます。代わりに、コンテナの起動の時間がかかります。UPSERT の DB ごとの違いは、関連記事「SQL UPSERTの書き方|PostgreSQL・MySQL・MERGE構文比較表」で扱っています。

Q. PostgreSQL の版はどう選びますか。 A. 本番と同じメジャー版を、タグで指定します(postgres:17-alpine など)。latest にすると、ある日から別の版でテストが動くことになります。

Q. テストが遅いです。 A. コンテナを static にしてクラスで共有する、Bean にしてアプリケーションコンテキストごとに共有する、ローカルでは reuse を使う、の順に効きます。テストクラスごとに設定(@MockitoBean の有無など)が違うと、コンテキストが別になり、コンテナも別に起動します。

Q. Controller のテストにも要りますか。 A. @WebMvcTest で Controller だけを確かめるテストは、DB を使わないので要りません。DB まで通すテストに使います。使い分けは、関連記事「Spring Bootテスト入門|@WebMvcTestとMockMvcの使い分け」で扱っています。

まとめ

  • 2.0 は依存が testcontainers-postgresql・testcontainers-junit-jupiter、クラスは org.testcontainers.postgresql.PostgreSQLContainer
  • Boot では spring-boot-testcontainers を足し、static なコンテナに @ServiceConnection
  • @ServiceConnection が使えないものは @DynamicPropertySource
  • 複数のテストクラスで共有するなら、@TestConfiguration の @Bean にする(Boot のドキュメントのおすすめ)
  • reuse はローカル専用。~/.testcontainers.properties で有効にする
  • GitHub Actions は ubuntu-latest でそのまま動く。Docker が無い環境は disabledWithoutDocker = true

参考・出典

確認日はいずれも 2026年10月13日です。

  • Testcontainers for Java ドキュメント:https://java.testcontainers.org/
  • Testcontainers(GitHub、ソースとドキュメント・タグ 2.0.5):https://github.com/testcontainers/testcontainers-java
  • Testcontainers 2.0.0 リリースノート:https://github.com/testcontainers/testcontainers-java/releases/tag/2.0.0
  • Testcontainers ドキュメント「Reusable Containers」:https://java.testcontainers.org/features/reuse/
  • Spring Boot ドキュメント「Testcontainers」:https://docs.spring.io/spring-boot/reference/testing/testcontainers.html
  • Spring Boot ドキュメント「Development-time Services」:https://docs.spring.io/spring-boot/reference/features/dev-services.html
  • Spring Boot ソース(タグ v4.1.1):https://github.com/spring-projects/spring-boot/tree/v4.1.1
  • GitHub Actions runner-images(Ubuntu 24.04):https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Testcontainersの使い方|Spring BootでPostgreSQL」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?