概要

単体テストだけでは拾えない不具合がある。コンポーネント同士の結合部分、HTTP リクエストからレスポンスまでの一連の流れ、データベースとの整合性――こうした層をカバーするのが統合テストと E2E テストの役割です。一方で「テストが遅い」「環境依存で CI が落ちる」という声も現場では少なくありません。この記事では、テストピラミッドの考え方を土台に、Spring Boot の @SpringBootTest と TestRestTemplate を使い、REST API の CRUD エンドポイントを実際に起動して検証する統合テストの書き方を示します。テスト用のポート設定、テストデータの初期化、レスポンスの検証まで、実務で迷いやすいポイントをコード付きで押さえます。

使いどころ

REST API の CRUD に対してリクエスト送信からレスポンスまで一気通貫で検証する統合テストを整備する

マイクロサービス間の API 呼び出しが正しいステータスコードを返すことを TestRestTemplate で確認する

既存の手動テスト手順を自動化し、CI パイプラインに組み込む

認証フィルタや例外ハンドラを含む HTTP の振る舞いを、アプリ起動込みで確認する

コントローラ、サービス、DB の結線が意図どおり動くかをデプロイ前にまとめて確認する

コード例

EmployeeApiIntegrationTest.java
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.annotation.DirtiesContext;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class EmployeeApiIntegrationTest {

    @Autowired TestRestTemplate restTemplate;

    record EmployeeResponse(Long id, String name, String department) {}
    record CreateEmployeeRequest(String name, String department) {}

    @Test @DisplayName("登録して取得できること")
    void createAndGet() {
        var req = new CreateEmployeeRequest("田中太郎", "開発部");
        var createRes = restTemplate.postForEntity("/api/employees", req, EmployeeResponse.class);
        assertEquals(HttpStatus.CREATED, createRes.getStatusCode());

        var getRes = restTemplate.getForEntity("/api/employees/" + createRes.getBody().id(), EmployeeResponse.class);
        assertEquals(HttpStatus.OK, getRes.getStatusCode());
        assertEquals("田中太郎", getRes.getBody().name());
    }

    @Test @DisplayName("削除できること")
    void delete() {
        var req = new CreateEmployeeRequest("鈴木一郎", "総務部");
        var created = restTemplate.postForEntity("/api/employees", req, EmployeeResponse.class);

        restTemplate.delete("/api/employees/" + created.getBody().id());
        var getRes = restTemplate.getForEntity("/api/employees/" + created.getBody().id(), String.class);
        assertEquals(HttpStatus.NOT_FOUND, getRes.getStatusCode());
    }

    @Test @DisplayName("存在しない ID は 404")
    void notFound() {
        var res = restTemplate.getForEntity("/api/employees/99999", String.class);
        assertEquals(HttpStatus.NOT_FOUND, res.getStatusCode());
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

record でレスポンス DTO を簡潔に定義できる。Spring Boot 3.x は Java 17 以上を要求する。

Java 17
// Java 17: record でレスポンス DTO を定義
record EmployeeResponse(Long id, String name, String department) {}
ResponseEntity<EmployeeResponse> response =
    restTemplate.getForEntity("/api/employees/1", EmployeeResponse.class);
assertEquals("田中太郎", response.getBody().name());

Library Comparison

TestRestTemplateSpring Boot アプリの統合テストで実際に HTTP リクエストを送りたいとき。コンテキスト起動が必要で単体テストより遅い。
MockMvcサーバーを起動せずにコントローラ層だけを高速にテストしたいとき。フィルタやエラーハンドリングの一部が実際の動作と異なる場合がある。
REST AssuredBDD スタイルで API テストを書きたいとき。レスポンスの検証 DSL が充実している。外部依存が増える。TestRestTemplate で十分なケースでは過剰。

注意点

@SpringBootTest はコンテキスト全体を起動するため実行時間が長い。統合テストは必要な範囲に絞る

webEnvironment = RANDOM_PORT を指定しないとポート競合で CI が失敗する

テスト間でDB状態を共有するとフレイキーテストの原因になる。@BeforeEach でデータを初期化する

すべてのテストで @SpringBootTest を使うとテストスイートの実行時間が膨れ上がる

TestRestTemplate はリダイレクトを自動で追わない設定がデフォルト

時刻や外部 API 応答などの不安定要素をそのまま含めると E2E テストが壊れやすい。固定化やスタブ化できる境界を見極める

単体テストで十分確認できる分岐まで統合テストへ持ち込むと、失敗時の原因切り分けが難しくなる

FAQ

@SpringBootTest と @WebMvcTest はどう使い分けますか。

コントローラ単体は @WebMvcTest、サービス層やDBを含む一気通貫テストは @SpringBootTest を使います。

TestRestTemplate と WebTestClient のどちらを使うべきですか。

Servlet ベースは TestRestTemplate、WebFlux ベースは WebTestClient が適しています。

統合テストの DB は本番と同じものを使うべきですか。

CI では H2 や Testcontainers で再現性を確保し、本番同等の検証はステージングで行うのが現実的です。

E2E テストはどこまで自動化すべきですか。

主要な業務フローや障害時の復旧動線など、手戻りコストが高い経路を優先するのが現実的です。すべてを UI 経由で自動化すると保守コストが急増します。

統合テストが遅くなったときは何から見直すべきですか。

まず @SpringBootTest の件数とスコープを見直します。次に DB 初期化や外部依存の起動コストを確認し、MockMvc や slice test に切り分けられる箇所を減らしていきます。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます