【Spring Boot】@DataJpaTestでリポジトリのテストを書く

Java

こんにちは、かつコーチです。

@Queryでカスタムクエリを書いたとき、「このJPQLは本当に意図通りのデータを取ってきているか」を確認したくなったことはありませんか。
筆者はクエリメソッドの命名を間違えて、意図と違う条件でデータを取得してしまい、結合テストの段階まで気づかなかった経験があります。

こうしたミスを早期に発見するために有効なのが、リポジトリ層に絞ってテストする@DataJpaTestです。
この記事では、@DataJpaTestの仕組みと、実際のクエリを検証する手順を解説します。

@DataJpaTestとは?

JPA関連のBeanだけを起動する仕組み

@DataJpaTestは、@EntityRepositoryインターフェース・JPA関連の設定だけをDIコンテナに登録するアノテーションです。
@Service@Controllerは起動しないため、リポジトリの動作確認に特化したテストが書けます。

@DataJpaTest
class ArticleRepositoryTest {
    // ArticleRepositoryとJPA関連のBeanだけが起動する
}

@WebMvcTestがコントローラ層に絞るのと同じ考え方で、@DataJpaTestはデータアクセス層に絞ったスライステストです。

デフォルトで組み込みDBに差し替わる

@DataJpaTestのもう一つの大きな特徴は、デフォルトで組み込みDB(H2などアプリケーションに埋め込んで使う軽量なデータベース)に自動的に差し替わることです。

application.propertiesで本番用にPostgreSQLやMySQLを設定していても、@DataJpaTestのテストではその設定が使われず、テスト用の組み込みDBが自動で起動・破棄されます。
これにより、本番DBに接続する心配をせず、安全にテストを実行できます。

基本の書き方・実装手順

依存関係にH2を追加する

組み込みDBとして広く使われるH2をテストスコープで依存関係に追加します。

// build.gradle
testImplementation 'com.h2database:h2'

testImplementationで追加することで、本番用のJARには含まれず、テスト実行時だけH2が使われるようにできます。

TestEntityManagerでテストデータを準備する

@DataJpaTestでは、TestEntityManager(テスト用にJPAのEntityManagerを扱いやすくしたクラス)が自動でDIされます。

package com.example.blog.repository;

import com.example.blog.entity.Article;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

import java.util.List;

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

@DataJpaTest
class ArticleRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private ArticleRepository articleRepository;

    @Test
    void findByIsPublishedTrue_returnsOnlyPublished() {
        Article published = new Article();
        published.setTitle("公開済み記事");
        published.setPublished(true);
        entityManager.persist(published);

        Article draft = new Article();
        draft.setTitle("下書き記事");
        draft.setPublished(false);
        entityManager.persist(draft);

        entityManager.flush(); // DBへ即座に反映させる

        List<Article> result = articleRepository.findByIsPublishedTrue();

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getTitle()).isEqualTo("公開済み記事");
    }
}

entityManager.persist()でテスト用データを登録し、flush()で即座にDBへ反映させてから検証するのが基本の流れです。
flush()を呼ばないと、実際にSQLが発行されるタイミングがJPAの内部管理に委ねられ、テストの意図と実行順序がずれることがあります。

クエリメソッドの結果を検証する

クエリメソッドの命名規則(findBy〜)が意図通りの条件になっているかを、実データで確認します。

@Test
void findByTitleContaining_returnsMatchingArticles() {
    Article article1 = new Article();
    article1.setTitle("Spring Boot入門");
    entityManager.persist(article1);

    Article article2 = new Article();
    article2.setTitle("Django入門");
    entityManager.persist(article2);

    entityManager.flush();

    List<Article> result = articleRepository.findByTitleContaining("Spring");

    assertThat(result).extracting(Article::getTitle)
            .containsExactly("Spring Boot入門");
}

extracting()はAssertJの機能で、リストの中から特定のプロパティだけを抽出して比較できます。
Entity同士のequalsを実装していなくても、プロパティ単位で検証できるため扱いやすい方法です。

よくあるつまずきポイント・エラー対処

❌ Before:本番用DBの設定がテストにも読み込まれてしまう

筆者が初めて@DataJpaTestを使ったとき、application.propertiesにPostgreSQLの接続情報しか書いておらず、依存関係にH2を追加し忘れたまま実行して以下のエラーが出ました。

java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: java.lang.ClassNotFoundException: org.h2.Driver

@DataJpaTestは組み込みDBへの自動差し替えを試みますが、クラスパス上に組み込みDBのドライバが存在しないと起動に失敗します。

✅ After:テストスコープでH2を依存関係に追加する

build.gradletestImplementation 'com.h2database:h2'を追加し、再度テストを実行すると正常に起動しました。

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'org.postgresql:postgresql' // 本番用
    testImplementation 'com.h2database:h2'  // テスト用
}

本番用ドライバとテスト用ドライバをスコープで分けておくことで、それぞれの環境に応じたDBが自動で選択されるようになります。
「テストが急にDB関連のエラーで失敗する」ときは、まずこの依存関係の設定を疑ってみてください。

応用・一歩先の使い方

@AutoConfigureTestDatabaseで実DBとの接続を維持する

MySQL固有の関数を使ったクエリなど、H2では再現できないSQLをテストしたい場合は、@AutoConfigureTestDatabaseで組み込みDBへの自動差し替えを無効化できます。

import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ArticleRepositoryMySqlSpecificTest {
    // application.propertiesの設定通り、実際のMySQLに接続してテストする
}

Replace.NONEを指定すると、application.propertiesで設定した本来のDB接続がそのまま使われます。
ただしCIなどの環境でテスト用DBを別途用意する必要があるため、通常はH2で十分な範囲に留め、必要な箇所だけこの設定を使うのが現実的です。

@Queryで書いたJPQLもTestEntityManagerで検証できる

@Queryアノテーションで書いたカスタムクエリも、同じ流れでテストできます。

@Test
void findRecentArticles_returnsInDescendingOrder() {
    // ... テストデータの準備 ...

    List<Article> result = articleRepository.findRecentArticles(3);

    assertThat(result).isSortedAccordingTo(
            (a, b) -> b.getCreatedAt().compareTo(a.getCreatedAt())
    );
}

isSortedAccordingTo()のようなAssertJの検証メソッドを使うと、並び順の意図まで含めてクエリの正しさを確認できます。
複雑なJOINやサブクエリを書いたときほど、こうしたテストを用意しておく価値が高くなります。

まとめ

この記事のポイント

  • @DataJpaTestはJPA関連のBeanだけをDIコンテナに登録するスライステスト
  • デフォルトで組み込みDB(H2など)に自動的に差し替わる
  • TestEntityManagerでテストデータを準備し、flush()で即座にDBへ反映させる
  • H2をtestImplementationで依存関係に追加しないと、DB差し替えに失敗する
  • 実DB固有の機能を検証したい場合は@AutoConfigureTestDatabase(replace = Replace.NONE)を使う

次に読むべき記事

サービス層のロジックをテストする方法は、次の「Mockitoを使ったサービス層のテスト」で解説します。

タグ: Spring Boot, 中級者向け, テスト

タイトルとURLをコピーしました