こんにちは、かつコーチです。
@Queryでカスタムクエリを書いたとき、「このJPQLは本当に意図通りのデータを取ってきているか」を確認したくなったことはありませんか。
筆者はクエリメソッドの命名を間違えて、意図と違う条件でデータを取得してしまい、結合テストの段階まで気づかなかった経験があります。
こうしたミスを早期に発見するために有効なのが、リポジトリ層に絞ってテストする@DataJpaTestです。
この記事では、@DataJpaTestの仕組みと、実際のクエリを検証する手順を解説します。
@DataJpaTestとは?
JPA関連のBeanだけを起動する仕組み
@DataJpaTestは、@Entity・Repositoryインターフェース・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.gradleにtestImplementation '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, 中級者向け, テスト