この章では、Spring の統合テストのサポートと単体テストのベストプラクティスについて説明します。Spring チームは、テスト駆動開発(TDD)を提唱しています。Spring チームは、Inversion of Control(IoC)を正しく使用すると、ユニットテストと統合テストの両方が簡単になることを確認しました(setter メソッドとクラス上の適切なコンストラクターが存在することで、テストを行う必要なく、簡単にワイヤリングできます)サービスロケータレジストリおよび同様の構造を設定します)。

1. Spring テストの概要

テストは、エンタープライズソフトウェア開発の不可欠な部分です。この章では、ユニットテストに IoC 原則によって付加される価値と、統合テストに対する Spring Framework のサポートの利点に焦点を当てます。(企業でのテストの徹底的な処理は、このリファレンスマニュアルの範囲を超えています。)

2. ユニットテスト

依存性注入により、従来の Java EE 開発の場合よりも、コードがコンテナーに依存しにくくなります。アプリケーションを構成する POJO は、Spring またはその他のコンテナーなしで、new 演算子を使用してインスタンス化されたオブジェクトを使用して、JUnit または TestNG テストでテスト可能である必要があります。モックオブジェクトを (他の価値あるテスト手法と組み合わせて)使用して、コードを単独でテストできます。Spring のアーキテクチャの推奨事項に従うと、コードベースのクリーンなレイヤー化とコンポーネント化により、ユニットテストが容易になります。例:DAO またはリポジトリインターフェースをスタブまたはモックすることにより、単体テストの実行中に永続データにアクセスする必要なく、サービスレイヤーオブジェクトをテストできます。

通常、設定するランタイムインフラストラクチャがないため、真のユニットテストは非常に迅速に実行されます。開発方法論の一部として真の単体テストを強調すると、生産性を高めることができます。IoC ベースのアプリケーションの効果的な単体テストを作成するために、テストの章のこのセクションは必要ない場合があります。ただし、特定のユニットテストシナリオでは、Spring Framework はモックオブジェクトとテストサポートクラスを提供します。これらについては、この章で説明します。

2.1. モックオブジェクト

Spring には、モック専用のパッケージがいくつか含まれています。

2.1.1. 環境

org.springframework.mock.env パッケージには、Environment および PropertySource 抽象化のモック実装が含まれています(Bean 定義プロファイルおよび PropertySource の抽象化を参照)。MockEnvironment および MockPropertySource は、環境固有のプロパティに依存するコードのコンテナー外テストの開発に役立ちます。

2.1.2. JNDI

org.springframework.mock.jndi パッケージには、テストスイートまたはスタンドアロンアプリケーション用の単純な JNDI 環境をセットアップするために使用できる JNDI SPI の部分的な実装が含まれています。たとえば、JDBC DataSource インスタンスが Java EE コンテナーの場合と同じテストコードで同じ JNDI 名にバインドされた場合、テストシナリオでアプリケーションコードと設定の両方を修正なしで再利用できます。

org.springframework.mock.jndi パッケージのモック JNDI サポートは、Simple-JNDI (GitHub) などのサードパーティからの完全なソリューションを推奨して、Spring Framework 5.2 の時点で正式に非推奨になりました。

2.1.3. サーブレット API

org.springframework.mock.web パッケージには、Web コンテキスト、コントローラー、フィルターのテストに役立つサーブレット API モックオブジェクトの包括的なセットが含まれています。これらのモックオブジェクトは、Spring の Web MVC フレームワークでの使用を対象としており、一般に動的なモックオブジェクト(EasyMock (英語) など)や代替の Servlet API モックオブジェクト(MockObjects (英語) など)よりも使いやすいです。

Spring Framework 5.0, 以降、org.springframework.mock.web のモックオブジェクトは Servlet 4.0 API に基づいています。

Spring MVC テストフレームワークは、モックサーブレット API オブジェクト上に構築され、Spring MVC の統合テストフレームワークを提供します。Spring MVC テストフレームワークを参照してください。

2.1.4. Spring Web リアクティブ

org.springframework.mock.http.server.reactive パッケージには、WebFlux アプリケーションで使用するための ServerHttpRequest および ServerHttpResponse のモック実装が含まれています。org.springframework.mock.web.server パッケージには、これらのモックリクエストおよびレスポンスオブジェクトに依存するモック ServerWebExchange が含まれています。

MockServerHttpRequest と MockServerHttpResponse は両方とも、サーバー固有の実装と同じ抽象基本クラスから拡張され、それらと動作を共有します。例:作成されたモックリクエストは不変ですが、ServerHttpRequest の mutate() メソッドを使用して、変更されたインスタンスを作成できます。

モックレスポンスが書き込み契約を適切に実装し、書き込み完了ハンドル(つまり、Mono<Void>)を返すために、デフォルトで Flux と cache().then() を使用します。これにより、データがバッファリングされ、テストでのアサーションに使用できるようになります。アプリケーションは、カスタム書き込み機能を設定できます(たとえば、無限ストリームをテストするため)。

WebTestClient は、モックのリクエストとレスポンスに基づいて構築され、HTTP サーバーなしで WebFlux アプリケーションのテストをサポートします。クライアントは、実行中のサーバーでのエンドツーエンドテストにも使用できます。

2.2. 単体テストのサポートクラス

Spring には、単体テストに役立つ多くのクラスが含まれています。それらは 2 つのカテゴリに分類されます。

2.2.1. 一般的なテストユーティリティ

org.springframework.test.util パッケージには、単体テストおよび統合テストで使用するためのいくつかの汎用ユーティリティが含まれています。

ReflectionTestUtils は、リフレクションベースのユーティリティメソッドのコレクションです。これらのメソッドは、アプリケーションコードのテスト時に定数の値の変更、public 以外のフィールドの設定、public 以外の setter メソッドの呼び出し、または public 以外の構成またはライフサイクルコールバックメソッドの呼び出しが必要なテストシナリオで使用できます。次のようなユースケース:

  • ドメインエンティティのプロパティの public setter メソッドではなく、private または protected フィールドアクセスを許可する ORM フレームワーク(JPA や Hibernate など)。

  • private または protected フィールド、setter メソッド、および構成メソッドの依存性注入を提供する Spring のアノテーション(@Autowired@Inject、および @Resource など)のサポート。

  • ライフサイクルコールバックメソッドに対する @PostConstruct や @PreDestroy などのアノテーションの使用。

AopTestUtils(Javadoc) は、AOP 関連のユーティリティメソッドのコレクションです。これらのメソッドを使用して、1 つ以上の Spring プロキシの背後に隠されている基礎となるターゲットオブジェクトへの参照を取得できます。例:EasyMock や Mockito などのライブラリを使用して Bean を動的なモックとして構成しており、モックが Spring プロキシにラップされている場合、基礎となるモックに直接アクセスして、そのモックに期待を構成し、検証を実行する必要がある場合があります。Spring のコア AOP ユーティリティについては、AopUtils(Javadoc) および AopProxyUtils(Javadoc) を参照してください。

2.2.2. Spring MVC テストユーティリティ

org.springframework.test.web パッケージには ModelAndViewAssert(Javadoc) が含まれています。ModelAndViewAssert(Javadoc) は、JUnit、TestNG、または Spring MVC ModelAndView オブジェクトを処理する単体テスト用のその他のテストフレームワークと組み合わせて使用できます。

Spring MVC コントローラーの単体テスト
Spring MVC Controller クラスを POJO として単体テストするには、Spring のサーブレット API モックから MockHttpServletRequestMockHttpSession と組み合わせた ModelAndViewAssert などを使用します。Spring MVC と REST Controller クラスの完全な統合テストを Spring MVC の WebApplicationContext 構成と組み合わせて行うには、代わりに Spring MVC テストフレームワークを使用してください。

3. 統合テスト

このセクション(この章の残りのほとんど)では、Spring アプリケーションの統合テストについて説明します。次のトピックが含まれます。

3.1. 概要

デプロイをアプリケーションサーバーに要求したり、他のエンタープライズインフラストラクチャに接続したりすることなく、いくつかの統合テストを実行できることが重要です。そうすることで、次のようなことをテストできます。

  • Spring IoC コンテナーコンテキストの正しい接続。

  • JDBC または ORM ツールを使用したデータアクセス。これには、SQL ステートメントの正確性、Hibernate クエリ、JPA エンティティマッピングなどが含まれます。

Spring Framework は、spring-test モジュールでの統合テストの第一級のサポートを提供します。実際の JAR ファイルの名前には、リリースバージョンが含まれる場合があり、取得元によっては長い org.springframework.test 形式になる場合もあります(説明については、依存関係管理のセクションを参照してください)。このライブラリには org.springframework.test パッケージが含まれています。org.springframework.test パッケージには、Spring コンテナーとの統合テストに役立つ貴重なクラスが含まれています。このテストは、アプリケーションサーバーまたはその他のデプロイ環境に依存しません。このようなテストは、単体テストよりも実行に時間がかかりますが、アプリケーションサーバーに対してデプロイに依存する同等の Selenium テストまたはリモートテストよりもはるかに高速です。

ユニットおよび統合テストのサポートは、アノテーション駆動型 Spring TestContext フレームワークの形式で提供されます。TestContext フレームワークは、使用中の実際のテストフレームワークにとらわれないため、JUnit、TestNG などを含むさまざまな環境でのテストの計測が可能です。

3.2. 統合テストのゴール

Spring の統合テストサポートの主なゴールは次のとおりです。

次のいくつかのセクションでは、各ゴールについて説明し、実装と構成の詳細へのリンクを提供します。

3.2.1. コンテキスト管理とキャッシュ

Spring TestContext フレームワークは、Spring ApplicationContext インスタンスと WebApplicationContext インスタンスの一貫したロード、およびこれらのコンテキストのキャッシングを提供します。Spring 自体のオーバーヘッドではなく、Spring コンテナーによってインスタンス化されたオブジェクトのインスタンス化に時間がかかるため、起動時間が課題になる可能性があるため、ロードされたコンテキストのキャッシュのサポートが重要です。例:Hibernate マッピングファイルが 50 から 100 のプロジェクトでは、マッピングファイルの読み込みに 10 から 20 秒かかる場合があり、すべてのテストフィクスチャですべてのテストを実行する前にそのコストが発生すると、全体的なテスト実行が遅くなり、開発者の生産性が低下します。

通常、テストクラスは、XML または Groovy 構成メタデータのリソースの場所の配列(多くの場合、クラスパス内)またはアプリケーションの構成に使用されるコンポーネントクラスの配列を宣言します。これらのロケーションまたはクラスは、web.xml または実動デプロイのその他の構成ファイルで指定されているものと同じまたは類似しています。

デフォルトでは、ロードされると、構成された ApplicationContext は各テストで再利用されます。セットアップコストはテストスイートごとに 1 回だけ発生し、その後のテスト実行ははるかに高速です。このコンテキストでは、「テストスイート」という用語は、すべてのテストが同じ JVM で実行されることを意味します。たとえば、特定のプロジェクトまたはモジュールの Ant、Maven、または Gradle ビルドから実行されるすべてのテストです。まれに、テストによってアプリケーションコンテキストが破損し、(たとえば、Bean 定義またはアプリケーションオブジェクトの状態を変更することによって)リロードが必要な場合、TestContext フレームワークは、次を実行する前に構成をリロードし、アプリケーションコンテキストを再構築するように構成できます。テスト。

TestContext フレームワークを使用したコンテキスト管理およびコンテキストキャッシングを参照してください。

3.2.2. テストフィクスチャの依存性注入

TestContext フレームワークがアプリケーションコンテキストをロードするとき、依存性注入を使用してテストクラスのインスタンスをオプションで構成できます。これは、アプリケーションコンテキストから事前に構成された Bean を使用して、テストフィクスチャをセットアップするための便利なメカニズムを提供します。ここでの大きな利点は、さまざまなテストシナリオ(たとえば、Spring 管理のオブジェクトグラフ、トランザクションプロキシ、DataSource インスタンスなどの構成)でアプリケーションコンテキストを再利用できるため、個々のテストで複雑なテストフィクスチャセットアップを複製する必要がないことです。ケース。

例として、Title ドメインエンティティのデータアクセスロジックを実装するクラス(HibernateTitleRepository)があるシナリオを考えます。次の領域をテストする統合テストを作成します。

  • Spring 構成 : 基本的に、HibernateTitleRepository Bean の構成に関連するものはすべて正しく存在していますか?

  • Hibernate マッピングファイルの構成:すべてが正しくマッピングされており、適切な遅延読み込み設定が行われていますか?

  • HibernateTitleRepository のロジック:このクラスの構成済みインスタンスは予想どおりに機能しますか?

テストフィクスチャと TestContext フレームワークの依存性注入を参照してください。

3.2.3. トランザクション管理

実際のデータベースにアクセスするテストの一般的な課題の 1 つは、永続ストアの状態への影響です。開発データベースを使用する場合でも、状態の変更は将来のテストに影響を与える場合があります。また、永続データの挿入や変更などの多くの操作は、トランザクションの外部では実行(検証)できません。

TestContext フレームワークはこの課題に対処します。デフォルトでは、フレームワークは各テストのトランザクションを作成してロールバックします。トランザクションの存在を想定できるコードを作成できます。テストでトランザクション的にプロキシされたオブジェクトを呼び出す場合、設定されたトランザクションのセマンティクスに従って、それらは正しく動作します。さらに、テスト用に管理されているトランザクション内で実行中にテストメソッドが選択したテーブルの内容を削除すると、デフォルトでトランザクションがロールバックされ、データベースはテスト実行前の状態に戻ります。テストのアプリケーションコンテキストで定義された PlatformTransactionManager Bean を使用して、テストにトランザクションサポートが提供されます。

トランザクションをコミットする場合(通常ではありませんが、特定のテストでデータベースにデータを入力または変更する場合に役立つことがあります)、@Commit アノテーションを使用して、TestContext フレームワークにトランザクションをロールバックする代わりにコミットさせるように指示できます。

TestContext フレームワークを使用したトランザクション管理を参照してください。

3.2.4. 統合テストのサポートクラス

Spring TestContext フレームワークは、統合テストの記述を簡素化するいくつかの abstract サポートクラスを提供します。これらの基本テストクラスは、テストフレームワークへの明確に定義されたフックと、便利なインスタンス変数およびメソッドを提供します。

  • ApplicationContext。明示的な Bean ルックアップを実行するか、コンテキスト全体の状態をテストします。

  • SQL ステートメントを実行してデータベースを照会するための JdbcTemplate このようなクエリを使用して、データベース関連のアプリケーションコードの実行前と実行後の両方でデータベースの状態を確認できます。Spring は、そのようなクエリがアプリケーションコードと同じトランザクションのスコープで実行されるようにします。ORM ツールと組み合わせて使用する場合は、必ず検知を避けてください。

さらに、プロジェクト固有のインスタンス変数とメソッドを使用して、独自のカスタムアプリケーション全体のスーパークラスを作成することもできます。

TestContext フレームワークのサポートクラスを参照してください。

3.3. JDBC テストのサポート

org.springframework.test.jdbc パッケージには JdbcTestUtils が含まれています。JdbcTestUtils は、標準のデータベーステストシナリオを簡素化することを目的とした JDBC 関連のユーティリティ関数のコレクションです。具体的には、JdbcTestUtils は次の静的ユーティリティメソッドを提供します。

  • countRowsInTable(..): 指定されたテーブルの行数をカウントします。

  • countRowsInTableWhere(..): 指定された WHERE 句を使用して、指定されたテーブルの行数をカウントします。

  • deleteFromTables(..): 指定されたテーブルからすべての行を削除します。

  • deleteFromTableWhere(..): 指定された WHERE 句を使用して、指定されたテーブルから行を削除します。

  • dropTables(..): 指定したテーブルを削除します。

AbstractTransactionalJUnit4SpringContextTests および AbstractTransactionalTestNGSpringContextTests は、JdbcTestUtils の前述のメソッドに委譲する便利なメソッドを提供します。

spring-jdbc モジュールは、データベースと対話する統合テストで使用できる組み込みデータベースの構成と起動のサポートを提供します。詳細については、組み込みデータベースのサポートおよび組み込みデータベースを使用したデータアクセスロジックのテストを参照してください。

3.4. アノテーション

このセクションでは、Spring アプリケーションをテストするときに使用できるアノテーションについて説明します。次のトピックが含まれます。

3.4.1. Spring テストアノテーション

Spring Framework は、TestContext フレームワークと組み合わせてユニットおよび統合テストで使用できる次の Spring 固有のアノテーションセットを提供します。デフォルトの属性値、属性エイリアス、およびその他の詳細を含む詳細については、対応する javadoc を参照してください。

Spring のテストアノテーションには次のものが含まれます。

@BootstrapWith

@BootstrapWith は、Spring TestContext フレームワークのブートストラップ方法を構成するために使用できるクラスレベルのアノテーションです。具体的には、@BootstrapWith を使用してカスタム TestContextBootstrapper を指定できます。詳細については、TestContext フレームワークのブートストラップに関するセクションを参照してください。

@ContextConfiguration

@ContextConfiguration は、統合テスト用に ApplicationContext をロードおよび構成する方法を決定するために使用されるクラスレベルのメタデータを定義します。具体的には、@ContextConfiguration は、コンテキストのロードに使用されるアプリケーションコンテキストリソース locations またはコンポーネント classes を宣言します。

リソースの場所は通常、クラスパスにある XML 構成ファイルまたは Groovy スクリプトですが、コンポーネントクラスは通常 @Configuration クラスです。ただし、リソースの場所はファイルシステム内のファイルとスクリプトを参照することもでき、コンポーネントクラスは @Component クラス、@Service クラスなどにすることができます。詳細については、コンポーネントクラスを参照してください。

次の例は、XML ファイルを参照する @ContextConfiguration アノテーションを示しています。

Java
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
    // class body...
}
1XML ファイルを参照します。
Kotlin
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
    // class body...
}
1XML ファイルを参照します。

次の例は、クラスを参照する @ContextConfiguration アノテーションを示しています。

Java
@ContextConfiguration(classes = TestConfig.class) (1)
class ConfigClassApplicationContextTests {
    // class body...
}
1 クラスを参照します。
Kotlin
@ContextConfiguration(classes = [TestConfig::class]) (1)
class ConfigClassApplicationContextTests {
    // class body...
}
1 クラスを参照します。

リソースの場所またはコンポーネントクラスを宣言する代わりに、またはそれに加えて、@ContextConfiguration を使用して ApplicationContextInitializer クラスを宣言できます。次の例は、このような場合を示しています。

Java
@ContextConfiguration(initializers = CustomContextIntializer.class) (1)
class ContextInitializerTests {
    // class body...
}
Kotlin
@ContextConfiguration(initializers = [CustomContextIntializer::class]) (1)
class ContextInitializerTests {
    // class body...
}
1 初期化子クラスを宣言します。

オプションで @ContextConfiguration を使用して、ContextLoader 戦略を宣言することもできます。ただし、デフォルトのローダーは initializers およびリソース locations またはコンポーネント classes のいずれかをサポートするため、通常はローダーを明示的に構成する必要はありません。

次の例では、場所とローダーの両方を使用しています。

Java
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) (1)
class CustomLoaderXmlApplicationContextTests {
    // class body...
}
1 ロケーションとカスタムローダーの両方を設定します。
Kotlin
@ContextConfiguration("/test-context.xml", loader = CustomContextLoader::class) (1)
class CustomLoaderXmlApplicationContextTests {
    // class body...
}
1 ロケーションとカスタムローダーの両方を設定します。
@ContextConfiguration は、リソースの場所または構成クラスの継承と、スーパークラスによって宣言されたコンテキスト初期化子のサポートを提供します。

詳細については、コンテキスト管理および @ContextConfiguration javadoc を参照してください。

@WebAppConfiguration

@WebAppConfiguration は、統合テスト用にロードされた ApplicationContext が WebApplicationContext であることを宣言するために使用できるクラスレベルのアノテーションです。テストクラスに @WebAppConfiguration が存在するだけで、Web アプリケーションのルートへのパス(つまり、リソースベースパス)に "file:src/main/webapp" のデフォルト値を使用して、WebApplicationContext がテスト用にロードされます。リソースベースパスは、テストの WebApplicationContext の ServletContext として機能する MockServletContext を作成するためにバックグラウンドで使用されます。

次の例は、@WebAppConfiguration アノテーションの使用方法を示しています。

Java
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
    // class body...
}
Kotlin
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
    // class body...
}
1@WebAppConfiguration アノテーション。

デフォルトをオーバーライドするには、暗黙の value 属性を使用して、異なるベースリソースパスを指定できます。classpath: と file: の両方のリソースプレフィックスがサポートされています。リソースプレフィックスが指定されていない場合、パスはファイルシステムリソースであると見なされます。次の例は、クラスパスリソースを指定する方法を示しています。

Java
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
    // class body...
}
1 クラスパスリソースの指定。
Kotlin
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
    // class body...
}
1 クラスパスリソースの指定。

@WebAppConfiguration は、単一のテストクラス内またはテストクラス階層内で、@ContextConfiguration と組み合わせて使用する必要があることに注意してください。詳細については、@WebAppConfiguration(Javadoc) javadoc を参照してください。

@ContextHierarchy

@ContextHierarchy は、統合テスト用に ApplicationContext インスタンスの階層を定義するために使用されるクラスレベルのアノテーションです。@ContextHierarchy は、1 つまたは複数の @ContextConfiguration インスタンスのリストで宣言する必要があります。各インスタンスは、コンテキスト階層のレベルを定義します。次の例は、単一のテストクラス内で @ContextHierarchy を使用する方法を示しています(@ContextHierarchy はテストクラス階層内でも使用できます)。

Java
@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
    // class body...
}
Kotlin
@ContextHierarchy(
    ContextConfiguration("/parent-config.xml"),
    ContextConfiguration("/child-config.xml"))
class ContextHierarchyTests {
    // class body...
}
Java
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = AppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
    // class body...
}
Kotlin
@WebAppConfiguration
@ContextHierarchy(
        ContextConfiguration(classes = [AppConfig::class]),
        ContextConfiguration(classes = [WebConfig::class]))
class WebIntegrationTests {
    // class body...
}

テストクラス階層内のコンテキスト階層の特定のレベルの構成をマージまたはオーバーライドする必要がある場合は、クラス階層の対応する各レベルで @ContextConfiguration の name 属性に同じ値を指定して、そのレベルに明示的に名前を付ける必要があります。さらなる例については、コンテキストの階層および @ContextHierarchy(Javadoc) javadoc を参照してください。

@ActiveProfiles

@ActiveProfiles は、統合テスト用に ApplicationContext をロードするときに、どの Bean 定義プロファイルをアクティブにする必要があるかを宣言するために使用されるクラスレベルのアノテーションです。

次の例は、dev プロファイルがアクティブであることを示しています。

Java
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
    // class body...
}
1dev プロファイルがアクティブであることを示します。
Kotlin
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
    // class body...
}
1dev プロファイルがアクティブであることを示します。

次の例は、dev プロファイルと integration プロファイルの両方がアクティブであることを示しています。

Java
@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) (1)
class DeveloperIntegrationTests {
    // class body...
}
1dev および integration プロファイルがアクティブであることを示します。
Kotlin
@ContextConfiguration
@ActiveProfiles(["dev", "integration"]) (1)
class DeveloperIntegrationTests {
    // class body...
}
1dev および integration プロファイルがアクティブであることを示します。
@ActiveProfiles は、デフォルトでスーパークラスによって宣言されたアクティブな Bean 定義プロファイルの継承をサポートします。カスタム ActiveProfilesResolver を実装し、@ActiveProfiles の resolver 属性を使用して登録することにより、アクティブな Bean 定義プロファイルをプログラムで解決することもできます。

例と詳細については、環境プロファイルを使用したコンテキスト設定@ActiveProfiles(Javadoc) javadoc を参照してください。

@TestPropertySource

@TestPropertySource は、統合テスト用に読み込まれた ApplicationContext の Environment の PropertySources のセットに追加されるプロパティファイルとインラインプロパティの場所を構成するために使用できるクラスレベルのアノテーションです。

次の例は、クラスパスからプロパティファイルを宣言する方法を示しています。

Java
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 クラスパスのルートにある test.properties からプロパティを取得します。
Kotlin
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 クラスパスのルートにある test.properties からプロパティを取得します。

次の例は、インラインプロパティを宣言する方法を示しています。

Java
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) (1)
class MyIntegrationTests {
    // class body...
}
1timezone および port プロパティを宣言します。
Kotlin
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) (1)
class MyIntegrationTests {
    // class body...
}
1timezone および port プロパティを宣言します。

例と詳細については、テストプロパティソースを使用したコンテキスト構成を参照してください。

@DynamicPropertySource

@DynamicPropertySource は、統合テスト用にロードされた ApplicationContext の Environment の PropertySources のセットに追加される動的プロパティを登録するために使用できるメソッドレベルのアノテーションです。動的プロパティは、プロパティの値が事前にわからない場合に役立ちます。たとえば、プロパティがテストコンテナー (英語) プロジェクトによって管理されるコンテナーなどの外部リソースによって管理される場合です。

次の例は、動的プロパティを登録する方法を示しています。

Java
@ContextConfiguration
class MyIntegrationTests {

    static MyExternalServer server = // ...

    @DynamicPropertySource (1)
    static void dynamicProperties(DynamicPropertyRegistry registry) { (2)
        registry.add("server.port", server::getPort); (3)
    }

    // tests ...
}
1@DynamicPropertySource で static メソッドにアノテーションを付けます。
2 引数として DynamicPropertyRegistry を受け入れます。
3 サーバーから遅延取得される動的 server.port プロパティを登録します。
Kotlin
@ContextConfiguration
class MyIntegrationTests {

    companion object {

        @JvmStatic
        val server: MyExternalServer = // ...

        @DynamicPropertySource (1)
        @JvmStatic
        fun dynamicProperties(registry: DynamicPropertyRegistry) { (2)
            registry.add("server.port", server::getPort) (3)
        }
    }

    // tests ...
}
1@DynamicPropertySource で static メソッドにアノテーションを付けます。
2 引数として DynamicPropertyRegistry を受け入れます。
3 サーバーから遅延取得される動的 server.port プロパティを登録します。

詳細については、動的プロパティソースを使用したコンテキスト構成を参照してください。

@DirtiesContext

@DirtiesContext は、基礎となる Spring ApplicationContext がテストの実行中にダーティになったこと(つまり、テストが何らかの方法で変更または破損した - シングルトン Bean の状態を変更するなど)を閉じる必要があることを示します。アプリケーションコンテキストがダーティとしてマークされると、テストフレームワークのキャッシュから削除され、閉じられます。結果として、基盤となる Spring コンテナーは、同じ構成メタデータを持つコンテキストを必要とする後続のテストのために再構築されます。

@DirtiesContext は、同じクラスまたはクラス階層内でクラスレベルとメソッドレベルの両方のアノテーションとして使用できます。このようなシナリオでは、ApplicationContext は、構成された methodMode および classMode に応じて、そのようなアノテーション付きメソッドの前後または現在のテストクラスの前後にダーティとしてマークされます。

次の例は、さまざまな構成シナリオでコンテキストがダーティになる場合を説明しています。

  • 現在のテストクラスの前で、クラスモードが BEFORE_CLASS に設定されているクラスで宣言された場合。

    Java
    @DirtiesContext(classMode = BEFORE_CLASS) (1)
    class FreshContextTests {
        // some tests that require a new Spring container
    }
    1 現在のテストクラスの前のコンテキストをダーティにします。
    Kotlin
    @DirtiesContext(classMode = BEFORE_CLASS) (1)
    class FreshContextTests {
        // some tests that require a new Spring container
    }
    1 現在のテストクラスの前のコンテキストをダーティにします。
  • 現在のテストクラスの後、クラスモードが AFTER_CLASS (つまり、デフォルトクラスモード)に設定されたクラスで宣言された場合。

    Java
    @DirtiesContext (1)
    class ContextDirtyingTests {
        // some tests that result in the Spring container being dirtied
    }
    1 現在のテストクラスの後にコンテキストをダーティにします。
    Kotlin
    @DirtiesContext (1)
    class ContextDirtyingTests {
        // some tests that result in the Spring container being dirtied
    }
    1 現在のテストクラスの後にコンテキストをダーティにします。
  • クラスモードが BEFORE_EACH_TEST_METHOD. に設定されたクラスで宣言された場合、現在のテストクラスの各テストメソッドの前

    Java
    @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1)
    class FreshContextTests {
        // some tests that require a new Spring container
    }
    1 各テストメソッドの前にコンテキストをダーティにします。
    Kotlin
    @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1)
    class FreshContextTests {
        // some tests that require a new Spring container
    }
    1 各テストメソッドの前にコンテキストをダーティにします。
  • 現在のテストクラスの各テストメソッドの後、クラスモードが AFTER_EACH_TEST_METHOD. に設定されたクラスで宣言された場合

    Java
    @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1)
    class ContextDirtyingTests {
        // some tests that result in the Spring container being dirtied
    }
    1 各テストメソッドの後にコンテキストをダーティにします。
    Kotlin
    @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1)
    class ContextDirtyingTests {
        // some tests that result in the Spring container being dirtied
    }
    1 各テストメソッドの後にコンテキストをダーティにします。
  • 現在のテストの前、メソッドモードが BEFORE_METHOD に設定されたメソッドで宣言された場合。

    Java
    @DirtiesContext(methodMode = BEFORE_METHOD) (1)
    @Test
    void testProcessWhichRequiresFreshAppCtx() {
        // some logic that requires a new Spring container
    }
    1 現在のテストメソッドの前のコンテキストをダーティにします。
    Kotlin
    @DirtiesContext(methodMode = BEFORE_METHOD) (1)
    @Test
    fun testProcessWhichRequiresFreshAppCtx() {
        // some logic that requires a new Spring container
    }
    1 現在のテストメソッドの前のコンテキストをダーティにします。
  • 現在のテストの後、メソッドモードが AFTER_METHOD (つまり、デフォルトのメソッドモード)に設定されたメソッドで宣言されたとき。

    Java
    @DirtiesContext (1)
    @Test
    void testProcessWhichDirtiesAppCtx() {
        // some logic that results in the Spring container being dirtied
    }
    1 現在のテストメソッドの後にコンテキストをダーティにします。
    Kotlin
    @DirtiesContext (1)
    @Test
    fun testProcessWhichDirtiesAppCtx() {
        // some logic that results in the Spring container being dirtied
    }
    1 現在のテストメソッドの後にコンテキストをダーティにします。

@ContextHierarchy を使用してコンテキストがコンテキスト階層の一部として構成されているテストで @DirtiesContext を使用する場合、hierarchyMode フラグを使用してコンテキストキャッシュのクリア方法を制御できます。デフォルトでは、現在のレベルだけでなく、現在のテストに共通の祖先コンテキストを共有する他のすべてのコンテキスト階層も含めて、網羅的アルゴリズムを使用してコンテキストキャッシュをクリアします。共通の祖先コンテキストのサブ階層にあるすべての ApplicationContext インスタンスは、コンテキストキャッシュから削除され、閉じられます。特定のユースケースで徹底的なアルゴリズムが過剰である場合、次の例に示すように、より単純な現在のレベルのアルゴリズムを指定できます。

Java
@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
class BaseTests {
    // class body...
}

class ExtendedTests extends BaseTests {

    @Test
    @DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
    void test() {
        // some logic that results in the child context being dirtied
    }
}
1 現在のレベルのアルゴリズムを使用します。
Kotlin
@ContextHierarchy(
    ContextConfiguration("/parent-config.xml"),
    ContextConfiguration("/child-config.xml"))
open class BaseTests {
    // class body...
}

class ExtendedTests : BaseTests() {

    @Test
    @DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
    fun test() {
        // some logic that results in the child context being dirtied
    }
}
1 現在のレベルのアルゴリズムを使用します。

EXHAUSTIVE および CURRENT_LEVEL アルゴリズムの詳細については、DirtiesContext.HierarchyMode(Javadoc) javadoc を参照してください。

@TestExecutionListeners

@TestExecutionListeners は、TestContextManager に登録する必要がある TestExecutionListener 実装を構成するためのクラスレベルのメタデータを定義します。通常、@TestExecutionListeners は @ContextConfiguration と組み合わせて使用されます。

次の例は、2 つの TestExecutionListener 実装を登録する方法を示しています。

Java
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) (1)
class CustomTestExecutionListenerTests {
    // class body...
}
12 つの TestExecutionListener 実装を登録します。
Kotlin
@ContextConfiguration
@TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) (1)
class CustomTestExecutionListenerTests {
    // class body...
}
12 つの TestExecutionListener 実装を登録します。

デフォルトでは、@TestExecutionListeners は継承されたリスナーをサポートします。例と詳細については、javadoc を参照してください。

@Commit

@Commit は、テストメソッドが完了した後に、トランザクションテストメソッドのトランザクションをコミットする必要があることを示します。@Commit を @Rollback(false) の直接の代替として使用して、コードの意図をより明確に伝えることができます。@Rollback と同様に、@Commit はクラスレベルまたはメソッドレベルのアノテーションとして宣言することもできます。

次の例は、@Commit アノテーションの使用方法を示しています。

Java
@Commit (1)
@Test
void testProcessWithoutRollback() {
    // ...
}
1 テストの結果をデータベースにコミットします。
Kotlin
@Commit (1)
@Test
fun testProcessWithoutRollback() {
    // ...
}
1 テストの結果をデータベースにコミットします。
@Rollback

@Rollback は、テストメソッドの補完後にトランザクションテストメソッドのトランザクションをロールバックする必要があるかどうかを示します。true の場合、トランザクションはロールバックされます。それ以外の場合、トランザクションはコミットされます(@Commit も参照)。@Rollback が明示的に宣言されていない場合でも、Spring TestContext フレームワークの統合テストのロールバックはデフォルトで true になります。

@Rollback は、クラスレベルのアノテーションとして宣言されると、テストクラス階層内のすべてのテストメソッドのデフォルトロールバックセマンティクスを定義します。メソッドレベルのアノテーションとして宣言された場合、@Rollback は特定のテストメソッドのロールバックセマンティクスを定義し、潜在的にクラスレベルの @Rollback または @Commit セマンティクスをオーバーライドします。

次の例では、テストメソッドの結果はロールバックされません(つまり、結果はデータベースにコミットされます)。

Java
@Rollback(false) (1)
@Test
void testProcessWithoutRollback() {
    // ...
}
1 結果をロールバックしないでください。
Kotlin
@Rollback(false) (1)
@Test
fun testProcessWithoutRollback() {
    // ...
}
1 結果をロールバックしないでください。
@BeforeTransaction

@BeforeTransaction は、Spring の @Transactional アノテーションを使用してトランザクション内で実行するように構成されたテストメソッドの場合、トランザクションを開始する前にアノテーション付き void メソッドを実行する必要があることを示します。@BeforeTransaction メソッドは public である必要はなく、Java 8 ベースのインターフェースのデフォルトメソッドで宣言できます。

次の例は、@BeforeTransaction アノテーションの使用方法を示しています。

Java
@BeforeTransaction (1)
void beforeTransaction() {
    // logic to be run before a transaction is started
}
1 トランザクションの前にこのメソッドを実行します。
Kotlin
@BeforeTransaction (1)
fun beforeTransaction() {
    // logic to be run before a transaction is started
}
1 トランザクションの前にこのメソッドを実行します。
@AfterTransaction

@AfterTransaction は、Spring の @Transactional アノテーションを使用してトランザクション内で実行するように構成されたテストメソッドについて、トランザクションの終了後にアノテーション付き void メソッドを実行する必要があることを示します。@AfterTransaction メソッドは public である必要はなく、Java 8 ベースのインターフェースのデフォルトメソッドで宣言できます。

Java
@AfterTransaction (1)
void afterTransaction() {
    // logic to be run after a transaction has ended
}
1 トランザクションの後にこのメソッドを実行します。
Kotlin
@AfterTransaction (1)
fun afterTransaction() {
    // logic to be run after a transaction has ended
}
1 トランザクションの後にこのメソッドを実行します。
@Sql

@Sql は、テストクラスまたはテストメソッドにアノテーションを付けて、統合テスト中に特定のデータベースに対して実行される SQL スクリプトを構成するために使用されます。次の例は、その使用方法を示しています。

Java
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"}) (1)
void userTest() {
    // run code that relies on the test schema and test data
}
1 このテスト用に 2 つのスクリプトを実行します。
Kotlin
@Test
@Sql("/test-schema.sql", "/test-user-data.sql") (1)
fun userTest() {
    // run code that relies on the test schema and test data
}
1 このテスト用に 2 つのスクリプトを実行します。

詳細については、@Sql を使用して SQL スクリプトを宣言的に実行するを参照してください。

@SqlConfig

@SqlConfig は、@Sql アノテーションで構成された SQL スクリプトを解析および実行する方法を決定するために使用されるメタデータを定義します。次の例は、その使用方法を示しています。

Java
@Test
@Sql(
    scripts = "/test-user-data.sql",
    config = @SqlConfig(commentPrefix = "`", separator = "@@") (1)
)
void userTest() {
    // run code that relies on the test data
}
1SQL スクリプトでコメントプレフィックスとセパレータを設定します。
Kotlin
@Test
@Sql("/test-user-data.sql", config = SqlConfig(commentPrefix = "`", separator = "@@")) (1)
fun userTest() {
    // run code that relies on the test data
}
1SQL スクリプトでコメントプレフィックスとセパレータを設定します。
@SqlMergeMode

@SqlMergeMode は、テストクラスまたはテストメソッドにアノテーションを付けて、メソッドレベルの @Sql 宣言をクラスレベルの @Sql 宣言とマージするかどうかを構成するために使用されます。@SqlMergeMode がテストクラスまたはテストメソッドで宣言されていない場合、OVERRIDE マージモードがデフォルトで使用されます。OVERRIDE モードでは、メソッドレベルの @Sql 宣言がクラスレベルの @Sql 宣言を効果的にオーバーライドします。

メソッドレベルの @SqlMergeMode 宣言は、クラスレベルの宣言をオーバーライドすることに注意してください。

次の例は、クラスレベルで @SqlMergeMode を使用する方法を示しています。

Java
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    void standardUserProfile() {
        // run code that relies on test data set 001
    }
}
1 クラス内のすべてのテストメソッドについて、@Sql マージモードを MERGE に設定します。
Kotlin
@SpringJUnitConfig(TestConfig::class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    fun standardUserProfile() {
        // run code that relies on test data set 001
    }
}
1 クラス内のすべてのテストメソッドについて、@Sql マージモードを MERGE に設定します。

次の例は、メソッドレベルで @SqlMergeMode を使用する方法を示しています。

Java
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    @SqlMergeMode(MERGE) (1)
    void standardUserProfile() {
        // run code that relies on test data set 001
    }
}
1 特定のテストメソッドの @Sql マージモードを MERGE に設定します。
Kotlin
@SpringJUnitConfig(TestConfig::class)
@Sql("/test-schema.sql")
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    @SqlMergeMode(MERGE) (1)
    fun standardUserProfile() {
        // run code that relies on test data set 001
    }
}
1 特定のテストメソッドの @Sql マージモードを MERGE に設定します。
@SqlGroup

@SqlGroup は、いくつかの @Sql アノテーションを集約するコンテナーアノテーションです。@SqlGroup をネイティブで使用して、複数のネストされた @Sql アノテーションを宣言するか、Java 8 の反復可能なアノテーションのサポートと組み合わせて使用できます。@Sql は、同じクラスまたはメソッドで何度も宣言でき、暗黙的にこのコンテナーアノテーションを生成します。次の例は、SQL グループを宣言する方法を示しています。

Java
@Test
@SqlGroup({ (1)
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
void userTest() {
    // run code that uses the test schema and test data
}
1SQL スクリプトのグループを宣言します。
Kotlin
@Test
@SqlGroup( (1)
    Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
    Sql("/test-user-data.sql"))
fun userTest() {
    // run code that uses the test schema and test data
}
1SQL スクリプトのグループを宣言します。

3.4.2. 標準アノテーションのサポート

以下のアノテーションは、Spring TestContext フレームワークのすべての構成の標準セマンティクスでサポートされています。これらのアノテーションはテスト固有のものではなく、Spring Framework のどこでも使用できることに注意してください。

  • @Autowired

  • @Qualifier

  • @Value

  • JSR-250 が存在する場合、@Resource (javax.annotation)

  • JSR-250 が存在する場合、@ManagedBean (javax.annotation)

  • JSR-330 が存在する場合、@Inject (javax.inject)

  • JSR-330 が存在する場合、@Named (javax.inject)

  • JPA が存在する場合、@PersistenceContext (javax.persistence)

  • JPA が存在する場合、@PersistenceUnit (javax.persistence)

  • @Required

  • @Transactional (org.springframework.transaction.annotation) 属性サポートの制限

JSR-250 ライフサイクルアノテーション

Spring TestContext フレームワークでは、@PostConstruct および @PreDestroy を、ApplicationContext で構成されたアプリケーションコンポーネントの標準的なセマンティクスとともに使用できます。ただし、これらのライフサイクルアノテーションは、実際のテストクラス内での使用が制限されています。

テストクラス内のメソッドに @PostConstruct アノテーションが付けられている場合、そのメソッドは基礎となるテストフレームワークの前のメソッド(たとえば、JUnit Jupiter の @BeforeEach アノテーションが付けられたメソッド)の前に実行され、テストクラスのすべてのテストメソッドに適用されます。一方、テストクラス内のメソッドに @PreDestroy アノテーションが付けられている場合、そのメソッドは実行されません。テストクラス内では、@PostConstruct および @PreDestroy の代わりに、基礎となるテストフレームワークからのテストライフサイクルコールバックを使用することをお勧めします。

3.4.3. Spring JUnit 4 テストアノテーション

次のアノテーションは、SpringRunnerSpring の JUnit 4 ルール、または Spring の JUnit 4 サポートクラスと組み合わせて使用される場合にのみサポートされます。

@IfProfileValue

@IfProfileValue は、特定のテスト環境でアノテーション付きテストが有効になっていることを示します。設定された ProfileValueSource が、提供された name に一致する value を返す場合、テストは有効です。そうでない場合、テストは無効になり、事実上無視されます。

@IfProfileValue は、クラスレベル、メソッドレベル、またはその両方で適用できます。@IfProfileValue のクラスレベルの使用は、そのクラスまたはそのサブクラス内のメソッドのメソッドレベルの使用よりも優先されます。具体的には、テストは、クラスレベルとメソッドレベルの両方で有効になっている場合に有効になります。@IfProfileValue がないと、テストは暗黙的に有効になります。これは、JUnit 4 の @Ignore アノテーションのセマンティクスに類似していますが、@Ignore が存在すると常にテストが無効になる点が異なります。

次の例は、@IfProfileValue アノテーションを持つテストを示しています。

Java
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
    // some logic that should run only on Java VMs from Oracle Corporation
}
1 このテストは、Java ベンダーが「Oracle Corporation」の場合にのみ実行してください。
Kotlin
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
fun testProcessWhichRunsOnlyOnOracleJvm() {
    // some logic that should run only on Java VMs from Oracle Corporation
}
1 このテストは、Java ベンダーが「Oracle Corporation」の場合にのみ実行してください。

または、@IfProfileValue を values のリスト(OR セマンティクスを使用)で構成して、JUnit 4 環境のテストグループに対する TestNG のようなサポートを実現できます。次の例を考えてみましょう。

Java
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) (1)
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
    // some logic that should run only for unit and integration test groups
}
1 単体テストと統合テストに対してこのテストを実行します。
Kotlin
@IfProfileValue(name="test-groups", values=["unit-tests", "integration-tests"]) (1)
@Test
fun testProcessWhichRunsForUnitOrIntegrationTestGroups() {
    // some logic that should run only for unit and integration test groups
}
1 単体テストと統合テストに対してこのテストを実行します。
@ProfileValueSourceConfiguration

@ProfileValueSourceConfiguration は、@IfProfileValue アノテーションで構成されたプロファイル値を取得するときに使用する ProfileValueSource のタイプを指定するクラスレベルのアノテーションです。@ProfileValueSourceConfiguration がテスト用に宣言されていない場合、デフォルトで SystemProfileValueSource が使用されます。次の例は、@ProfileValueSourceConfiguration の使用方法を示しています。

Java
@ProfileValueSourceConfiguration(CustomProfileValueSource.class) (1)
public class CustomProfileValueSourceTests {
    // class body...
}
1 カスタムプロファイル値ソースを使用します。
Kotlin
@ProfileValueSourceConfiguration(CustomProfileValueSource::class) (1)
class CustomProfileValueSourceTests {
    // class body...
}
1 カスタムプロファイル値ソースを使用します。
@Timed

@Timed は、アノテーション付きのテストメソッドが指定された期間(ミリ秒単位)で実行を終了する必要があることを示します。テキストの実行時間が指定された期間を超えると、テストは失敗します。

期間には、テストメソッド自体の実行、テストの繰り返し(@Repeat を参照)、テストフィクスチャの設定または破棄が含まれます。次の例は、その使用方法を示しています。

Java
@Timed(millis = 1000) (1)
public void testProcessWithOneSecondTimeout() {
    // some logic that should not take longer than 1 second to run
}
1 テストの期間を 1 秒に設定します。
Kotlin
@Timed(millis = 1000) (1)
fun testProcessWithOneSecondTimeout() {
    // some logic that should not take longer than 1 second to run
}
1 テストの期間を 1 秒に設定します。

Spring の @Timed アノテーションのセマンティクスは、JUnit 4 の @Test(timeout=…​) サポートとは異なります。具体的には、JUnit 4 がテスト実行タイムアウトを処理する方法により(つまり、別の Thread でテストメソッドを実行することにより)、テストに時間がかかりすぎると @Test(timeout=…​) はテストをプリエンプティブに失敗します。一方、Spring の @Timed は、テストを先制的に失敗させるのではなく、失敗する前にテストの補完を待機します。

@Repeat

@Repeat は、アノテーション付きテストメソッドを繰り返し実行する必要があることを示します。テストメソッドが実行される回数は、アノテーションで指定されます。

繰り返される実行の範囲には、テストフィクスチャのセットアップまたは破棄だけでなく、テストメソッド自体の実行も含まれます。次の例は、@Repeat アノテーションの使用方法を示しています。

Java
@Repeat(10) (1)
@Test
public void testProcessRepeatedly() {
    // ...
}
1 このテストを 10 回繰り返します。
Kotlin
@Repeat(10) (1)
@Test
fun testProcessRepeatedly() {
    // ...
}
1 このテストを 10 回繰り返します。

3.4.4. Spring JUnit Jupiter テストアノテーション

次のアノテーションは、SpringExtension および JUnit Jupiter(つまり、JUnit 5 のプログラミングモデル)と組み合わせて使用する場合にのみサポートされます。

@SpringJUnitConfig

@SpringJUnitConfig は、JUnit Jupiter の @ExtendWith(SpringExtension.class) と Spring TestContext フレームワークの @ContextConfiguration を組み合わせた合成アノテーションです。クラスレベルで @ContextConfiguration のドロップイン置換として使用できます。構成オプションに関して、@ContextConfiguration と @SpringJUnitConfig の唯一の違いは、@SpringJUnitConfig の value 属性でコンポーネントクラスを宣言できることです。

次の例は、@SpringJUnitConfig アノテーションを使用して構成クラスを指定する方法を示しています。

Java
@SpringJUnitConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
    // class body...
}
1 構成クラスを指定します。
Kotlin
@SpringJUnitConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
    // class body...
}
1 構成クラスを指定します。

次の例は、@SpringJUnitConfig アノテーションを使用して構成ファイルの場所を指定する方法を示しています。

Java
@SpringJUnitConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringTests {
    // class body...
}
1 構成ファイルの場所を指定します。
Kotlin
@SpringJUnitConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringTests {
    // class body...
}
1 構成ファイルの場所を指定します。

詳細については、コンテキスト管理および @SpringJUnitConfig(Javadoc) および @ContextConfiguration の javadoc を参照してください。

@SpringJUnitWebConfig

@SpringJUnitWebConfig は、JUnit Jupiter の @ExtendWith(SpringExtension.class) と Spring TestContext フレームワークの @ContextConfiguration および @WebAppConfiguration を組み合わせた合成アノテーションです。クラスレベルで、@ContextConfiguration および @WebAppConfiguration のドロップイン置換として使用できます。構成オプションに関して、@ContextConfiguration と @SpringJUnitWebConfig の唯一の違いは、@SpringJUnitWebConfig の value 属性を使用してコンポーネントクラスを宣言できることです。さらに、@SpringJUnitWebConfig で resourcePath 属性を使用することによってのみ、@WebAppConfiguration から value 属性をオーバーライドできます。

次の例は、@SpringJUnitWebConfig アノテーションを使用して構成クラスを指定する方法を示しています。

Java
@SpringJUnitWebConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
    // class body...
}
1 構成クラスを指定します。
Kotlin
@SpringJUnitWebConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
    // class body...
}
1 構成クラスを指定します。

次の例は、@SpringJUnitWebConfig アノテーションを使用して構成ファイルの場所を指定する方法を示しています。

Java
@SpringJUnitWebConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringWebTests {
    // class body...
}
1 構成ファイルの場所を指定します。
Kotlin
@SpringJUnitWebConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringWebTests {
    // class body...
}
1 構成ファイルの場所を指定します。

詳細については、コンテキスト管理、および @SpringJUnitWebConfig(Javadoc) @ContextConfiguration(Javadoc) 、および @WebAppConfiguration(Javadoc) の javadoc を参照してください。

@TestConstructor

@TestConstructor は、テストのクラスコンストラクターのパラメーターをテストの ApplicationContext のコンポーネントからオートワイヤーする方法を構成するために使用される型レベルのアノテーションです。

@TestConstructor がテストクラスに存在しないかメタ存在しない場合、デフォルトのテストコンストラクターオートワイヤーモードが使用されます。デフォルトモードを変更する方法の詳細については、以下のヒントを参照してください。ただし、コンストラクターでの @Autowired のローカル宣言は、@TestConstructor とデフォルトモードの両方よりも優先されることに注意してください。

デフォルトのテストコンストラクターオートワイヤーモードの変更

デフォルトのテストコンストラクターオートワイヤーモードは、spring.test.constructor.autowire.mode JVM システムプロパティを all に設定することで変更できます。または、SpringProperties メカニズムを介してデフォルトモードを変更できます。

spring.test.constructor.autowire.mode プロパティが設定されていない場合、テストクラスコンストラクターは自動的にオートワイヤーされません。

Spring Framework 5.2, 以降、@TestConstructor は、JUnit Jupiter で使用する SpringExtension との組み合わせでのみサポートされます。多くの場合、SpringExtension は自動的に登録されます。たとえば、@SpringJUnitConfig や @SpringJUnitWebConfig などのアノテーションや、Spring Boot テストのさまざまなテスト関連のアノテーションを使用する場合などです。
@EnabledIf

@EnabledIf は、アノテーション付きの JUnit Jupiter テストクラスまたはテストメソッドが有効であり、提供された expression が true に評価される場合に実行する必要があることを通知するために使用されます。具体的には、式が Boolean.TRUE または true に等しい String (大文字小文字を無視)に評価される場合、テストは有効になります。クラスレベルで適用すると、そのクラス内のすべてのテストメソッドもデフォルトで自動的に有効になります。

式は次のいずれかです。

  • Spring 式言語(SpEL)式。例: @EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")

  • Spring Environment で使用可能なプロパティのプレースホルダー。例: @EnabledIf("${smoke.tests.enabled}")

  • テキストリテラル。例: @EnabledIf("true")

ただし、@EnabledIf("false") は @Disabled と同等であり、@EnabledIf("true") は論理的に無意味であるため、プロパティプレースホルダーの動的解決の結果ではないテキストリテラルはゼロの実用的な値であることに注意してください。

@EnabledIf をメタアノテーションとして使用して、カスタム合成アノテーションを作成できます。例:次のように、カスタム @EnabledOnMac アノテーションを作成できます。

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(
    expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
    reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}
Kotlin
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@EnabledIf(
        expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
        reason = "Enabled on Mac OS"
)
annotation class EnabledOnMac {}
@DisabledIf

@DisabledIf は、アノテーション付きの JUnit Jupiter テストクラスまたはテストメソッドが無効になっていることを通知するために使用され、提供された expression が true と評価される場合は実行されません。具体的には、式が Boolean.TRUE または true に等しい String (大文字と小文字を区別しない)と評価される場合、テストは無効になります。クラスレベルで適用すると、そのクラス内のすべてのテストメソッドも自動的に無効になります。

式は次のいずれかです。

  • Spring 式言語(SpEL)式。例: @DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")

  • Spring Environment で使用可能なプロパティのプレースホルダー。例: @DisabledIf("${smoke.tests.disabled}")

  • テキストリテラル。例: @DisabledIf("true")

ただし、@DisabledIf("true") は @Disabled と同等であり、@DisabledIf("false") は論理的に無意味であるため、プロパティプレースホルダーの動的解決の結果ではないテキストリテラルはゼロの実用的な値であることに注意してください。

@DisabledIf をメタアノテーションとして使用して、カスタム合成アノテーションを作成できます。例:次のように、カスタム @DisabledOnMac アノテーションを作成できます。

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(
    expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
    reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}
Kotlin
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@DisabledIf(
        expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
        reason = "Disabled on Mac OS"
)
annotation class DisabledOnMac {}

3.4.5. テスト用のメタアノテーションのサポート

ほとんどのテスト関連のアノテーションをメタアノテーションとして使用して、カスタム構成アノテーションを作成し、テストスイート全体の構成の重複を減らすことができます。

次のそれぞれを TestContext フレームワークと組み合わせてメタアノテーションとして使用できます。

  • @BootstrapWith

  • @ContextConfiguration

  • @ContextHierarchy

  • @ActiveProfiles

  • @TestPropertySource

  • @DirtiesContext

  • @WebAppConfiguration

  • @TestExecutionListeners

  • @Transactional

  • @BeforeTransaction

  • @AfterTransaction

  • @Commit

  • @Rollback

  • @Sql

  • @SqlConfig

  • @SqlMergeMode

  • @SqlGroup

  • @Repeat (JUnit 4 でのみサポート)

  • @Timed (JUnit 4 でのみサポート)

  • @IfProfileValue (JUnit 4 でのみサポート)

  • @ProfileValueSourceConfiguration (JUnit 4 でのみサポート)

  • @SpringJUnitConfig (JUnit Jupiter でのみサポート)

  • @SpringJUnitWebConfig (JUnit Jupiter でのみサポート)

  • @TestConstructor (JUnit Jupiter でのみサポート)

  • @EnabledIf (JUnit Jupiter でのみサポート)

  • @DisabledIf (JUnit Jupiter でのみサポート)

次の例を考えてみましょう。

Java
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }

@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
Kotlin
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }

JUnit 4 ベースのテストスイート全体で上記の構成を繰り返していることがわかった場合は、次のように、Spring の共通のテスト構成を集中化するカスタム合成アノテーションを導入することで、重複を減らすことができます。

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }

次に、次のように、カスタム @TransactionalDevTestConfig アノテーションを使用して、個々の JUnit 4 ベースのテストクラスの構成を簡素化できます。

Java
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }

@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
Kotlin
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class OrderRepositoryTests

@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class UserRepositoryTests

JUnit Jupiter を使用するテストを記述する場合、JUnit 5 のアノテーションもメタアノテーションとして使用できるため、コードの重複をさらに減らすことができます。次の例を考えてみましょう。

Java
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
Kotlin
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }

JUnit Jupiter ベースのテストスイート全体で上記の構成を繰り返していることがわかった場合、次のように、Spring と JUnit Jupiter の共通のテスト構成を集中化するカスタム構成アノテーションを導入することで、重複を減らすことができます。

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }

次に、次のように、カスタム @TransactionalDevTestConfig アノテーションを使用して、個々の JUnit Jupiter ベースのテストクラスの構成を簡素化できます。

Java
@TransactionalDevTestConfig
class OrderRepositoryTests { }

@TransactionalDevTestConfig
class UserRepositoryTests { }
Kotlin
@TransactionalDevTestConfig
class OrderRepositoryTests { }

@TransactionalDevTestConfig
class UserRepositoryTests { }

JUnit Jupiter は @Test@RepeatedTestParameterizedTest などのメタアノテーションとしての使用をサポートしているため、テストメソッドレベルでカスタム合成アノテーションを作成することもできます。例:JUnit Jupiter の @Test アノテーションと @Tag アノテーションを Spring の @Transactional アノテーションと組み合わせる合成アノテーションを作成する場合、次のように @TransactionalIntegrationTest アノテーションを作成できます。

Java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
annotation class TransactionalIntegrationTest { }

次に、カスタム @TransactionalIntegrationTest アノテーションを使用して、次のように、個々の JUnit Jupiter ベースのテストメソッドの構成を簡素化できます。

Java
@TransactionalIntegrationTest
void saveOrder() { }

@TransactionalIntegrationTest
void deleteOrder() { }
Kotlin
@TransactionalIntegrationTest
fun saveOrder() { }

@TransactionalIntegrationTest
fun deleteOrder() { }

詳細については、Spring アノテーションプログラミングモデル (GitHub) wiki ページを参照してください。

3.5. Spring TestContext フレームワーク

Spring TestContext フレームワーク(org.springframework.test.context パッケージにあります)は、使用中のテストフレームワークにとらわれない、汎用のアノテーション駆動型ユニットおよび統合テストのサポートを提供します。また、TestContext フレームワークは、構成よりも規約を非常に重視しており、アノテーションベースの構成でオーバーライドできる妥当なデフォルトを備えています。

一般的なテストインフラストラクチャに加えて、TestContext フレームワークは、JUnit 4、JUnit Jupiter(別名 JUnit 5)、および TestNG の明示的なサポートを提供します。JUnit 4 および TestNG の場合、Spring は abstract サポートクラスを提供します。さらに、Spring は、JUnit 4 用のカスタム JUnit Runner とカスタム JUnit Rules、および JUnit Jupiter 用のカスタム Extension を提供し、いわゆる POJO テストクラスを作成できるようにします。POJO テストクラスは、abstract サポートクラスなど、特定のクラス階層を継承する必要はありません。

次のセクションでは、TestContext フレームワークの内部の概要を説明します。フレームワークの使用のみに関心があり、独自のカスタムリスナーまたはカスタムローダーで拡張することに興味がない場合は、構成(コンテキスト管理依存性注入トランザクション管理)、サポートクラス、およびアノテーションサポートに直接アクセスしてください。セクション。

3.5.1. 主要な抽象化

フレームワークのコアは、TestContextManager クラス、TestContextTestExecutionListener、および SmartContextLoader インターフェースで構成されています。TestContextManager は、テストクラスごとに作成されます(たとえば、JUnit Jupiter の単一のテストクラス内のすべてのテストメソッドの実行用)。TestContextManager は、現在のテストのコンテキストを保持する TestContext を管理します。また、TestContextManager は、テストの進行に応じて TestContext の状態を更新し、TestExecutionListener 実装に委譲します。TestExecutionListener 実装は、依存関係の注入、トランザクションの管理などを提供することにより、実際のテスト実行を計測します。SmartContextLoader は、所定のテストクラスの ApplicationContext をロードするロールを果たします。さまざまな実装の詳細と例については、javadoc および Spring テストスイートを参照してください。

TestContext

TestContext は、テストが実行されるコンテキスト(使用中の実際のテストフレームワークに依存しない)をカプセル化し、それが担当するテストインスタンスにコンテキスト管理とキャッシュサポートを提供します。TestContext は、リクエストされた場合に SmartContextLoader に委譲して ApplicationContext をロードします。

TestContextManager

TestContextManager は、Spring TestContext フレームワークへのメインエントリポイントであり、単一の TestContext を管理し、明確に定義されたテスト実行ポイントで各登録済み TestExecutionListener にイベントを通知します。

  • 特定のテストフレームワークの「before before」または「before before」メソッドの前。

  • テストインスタンスの後処理。

  • 特定のテストフレームワークの「前」または「各前」メソッドの前。

  • テストメソッドの実行直前、ただしテストのセットアップ後。

  • テストメソッドの実行直後、テストの分解前。

  • 特定のテストフレームワークの「後」または「各後」メソッドの後。

  • 特定のテストフレームワークの「クラスの後」または「すべての後」のメソッドの後。

TestExecutionListener

TestExecutionListener は、リスナーが登録されている TestContextManager によって発行されたテスト実行イベントに反応するための API を定義します。TestExecutionListener 設定を参照してください。

コンテキストローダー

ContextLoader は、Spring TestContext フレームワークによって管理される統合テスト用に ApplicationContext をロードするための戦略インターフェースです。このインターフェースの代わりに SmartContextLoader を実装して、コンポーネントクラス、アクティブな Bean 定義プロファイル、テストプロパティソース、コンテキスト階層、および WebApplicationContext サポートをサポートする必要があります。

SmartContextLoader は、オリジナルの最小 ContextLoader SPI に取って代わる ContextLoader インターフェースの拡張です。具体的には、SmartContextLoader は、リソースの場所、コンポーネントクラス、またはコンテキスト初期化子の処理を選択できます。さらに、SmartContextLoader は、アクティブな Bean 定義プロファイルを設定し、ロードするコンテキストでプロパティソースをテストできます。

Spring は、次の実装を提供します。

  • DelegatingSmartContextLoader: 2 つのデフォルトローダーの 1 つ。テストクラスに対して宣言された構成、またはデフォルトの場所またはデフォルトの構成クラスの存在に応じて、内部で AnnotationConfigContextLoaderGenericXmlContextLoader、または GenericGroovyXmlContextLoader に委譲します。Groovy サポートは、Groovy がクラスパス上にある場合にのみ有効になります。

  • WebDelegatingSmartContextLoader: 2 つのデフォルトローダーの 1 つ。テストクラスに対して宣言された構成、またはデフォルトの場所またはデフォルトの構成クラスの存在に応じて、内部で AnnotationConfigWebContextLoaderGenericXmlWebContextLoader、または GenericGroovyXmlWebContextLoader に委譲します。Web ContextLoader は、@WebAppConfiguration がテストクラスに存在する場合にのみ使用されます。Groovy サポートは、Groovy がクラスパス上にある場合にのみ有効になります。

  • AnnotationConfigContextLoader: コンポーネントクラスから標準 ApplicationContext をロードします。

  • AnnotationConfigWebContextLoader: コンポーネントクラスから WebApplicationContext をロードします。

  • GenericGroovyXmlContextLoader: Groovy スクリプトまたは XML 構成ファイルのいずれかであるリソースの場所から標準 ApplicationContext をロードします。

  • GenericGroovyXmlWebContextLoader: Groovy スクリプトまたは XML 構成ファイルのいずれかであるリソースの場所から WebApplicationContext をロードします。

  • GenericXmlContextLoader: XML リソースの場所から標準 ApplicationContext をロードします。

  • GenericXmlWebContextLoader: XML リソースの場所から WebApplicationContext をロードします。

  • GenericPropertiesContextLoader: Java プロパティファイルから標準 ApplicationContext をロードします。

3.5.2. TestContext フレームワークのブートストラップ

Spring TestContext フレームワークの内部のデフォルト構成は、すべての一般的なユースケースで十分です。ただし、開発チームまたはサードパーティのフレームワークがデフォルトの ContextLoader を変更したり、カスタム TestContext または ContextCache を実装したり、ContextCustomizerFactory および TestExecutionListener 実装のデフォルトセットを拡張したりする場合があります。TestContext フレームワークの動作をこのように低レベルで制御するために、Spring はブートストラップ戦略を提供します。

TestContextBootstrapper は、TestContext フレームワークをブートストラップするための SPI を定義します。TestContextBootstrapper は、現在のテストの TestExecutionListener 実装をロードし、管理する TestContext を構築するために、TestContextManager によって使用されます。@BootstrapWith を直接またはメタアノテーションとして使用して、テストクラス(またはテストクラス階層)のカスタムブートストラップ戦略を構成できます。@BootstrapWith を使用してブートストラップが明示的に構成されていない場合、@WebAppConfiguration の存在に応じて、DefaultTestContextBootstrapper または WebTestContextBootstrapper のいずれかが使用されます。

TestContextBootstrapper SPI は将来(新しい要件に対応するため)変更される可能性があるため、実装者はこのインターフェースを直接実装せず、代わりに AbstractTestContextBootstrapper またはその具象サブクラスの 1 つを継承することを強くお勧めします。

3.5.3. TestExecutionListener 設定

Spring は、デフォルトで正確に次の順序で登録される次の TestExecutionListener 実装を提供します。

  • ServletTestExecutionListenerWebApplicationContext のサーブレット API モックを構成します。

  • DirtiesContextBeforeModesTestExecutionListener: 「前」モードの @DirtiesContext アノテーションを処理します。

  • DependencyInjectionTestExecutionListener: テストインスタンスに依存性注入を提供します。

  • DirtiesContextTestExecutionListener: 「after」モードの @DirtiesContext アノテーションを処理します。

  • TransactionalTestExecutionListener: トランザクションテストの実行にデフォルトのロールバックセマンティクスを提供します。

  • SqlScriptsTestExecutionListener@Sql アノテーションを使用して構成された SQL スクリプトを実行します。

  • EventPublishingTestExecutionListener: テストの実行イベントをテストの ApplicationContext に公開します(テスト実行イベントを参照)。

TestExecutionListener 実装の登録

@TestExecutionListeners アノテーションを使用して、テストクラスとそのサブクラスの TestExecutionListener 実装を登録できます。詳細と例については、アノテーションサポート@TestExecutionListeners(Javadoc) の javadoc を参照してください。

デフォルトの TestExecutionListener 実装の自動検出

@TestExecutionListeners を使用して TestExecutionListener 実装を登録することは、限られたテストシナリオで使用されるカスタムリスナーに適しています。ただし、テストスイート全体でカスタムリスナーを使用する必要がある場合は、面倒になります。この課題は、SpringFactoriesLoader メカニズムによるデフォルトの TestExecutionListener 実装の自動検出のサポートにより解決されています。

具体的には、spring-test モジュールは、META-INF/spring.factories プロパティファイルの org.springframework.test.context.TestExecutionListener キーで、すべてのコアデフォルト TestExecutionListener 実装を宣言します。サードパーティのフレームワークと開発者は、独自の META-INF/spring.factories プロパティファイルを介して、同じ方法で独自の TestExecutionListener 実装をデフォルトリスナーのリストに提供できます。

TestExecutionListener 実装のオーダー

TestContext フレームワークが前述の SpringFactoriesLoader メカニズムを介してデフォルト TestExecutionListener 実装を発見すると、Spring の AnnotationAwareOrderComparator を使用してインスタンス化されたリスナーがソートされ、Spring の Ordered インターフェースと @Order アノテーションが順序付けされます。AbstractTestExecutionListener および Spring が提供するすべてのデフォルト TestExecutionListener 実装は、適切な値で Ordered を実装します。サードパーティのフレームワークと開発者は、Ordered を実装するか @Order を宣言することにより、デフォルトの TestExecutionListener 実装が適切な順序で登録されていることを確認する必要があります。各コアリスナーに割り当てられる値の詳細については、コアのデフォルト TestExecutionListener 実装の getOrder() メソッドの javadoc を参照してください。

TestExecutionListener 実装のマージ

カスタム TestExecutionListener が @TestExecutionListeners を介して登録されている場合、デフォルトのリスナーは登録されません。最も一般的なテストシナリオでは、これにより、開発者は、カスタムリスナーに加えて、すべてのデフォルトリスナーを手動で宣言する必要があります。次のリストは、このスタイルの構成を示しています。

Java
@ContextConfiguration
@TestExecutionListeners({
    MyCustomTestExecutionListener.class,
    ServletTestExecutionListener.class,
    DirtiesContextBeforeModesTestExecutionListener.class,
    DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    SqlScriptsTestExecutionListener.class
})
class MyTest {
    // class body...
}
Kotlin
@ContextConfiguration
@TestExecutionListeners(
    MyCustomTestExecutionListener::class,
    ServletTestExecutionListener::class,
    DirtiesContextBeforeModesTestExecutionListener::class,
    DependencyInjectionTestExecutionListener::class,
    DirtiesContextTestExecutionListener::class,
    TransactionalTestExecutionListener::class,
    SqlScriptsTestExecutionListener::class
)
class MyTest {
    // class body...
}

このアプローチの課題は、開発者がデフォルトで登録されているリスナーを正確に知っている必要があることです。さらに、デフォルトリスナーのセットはリリースごとに変更できます。たとえば、SqlScriptsTestExecutionListener は Spring Framework 4.1, で導入され、DirtiesContextBeforeModesTestExecutionListener は Spring Framework 4.2. で導入されます。さらに、Spring Boot や Spring Security などのサードパーティフレームワークは、前述の自動検出を使用して独自のデフォルト TestExecutionListener 実装を登録しますメカニズム

すべてのデフォルトリスナーを認識して再宣言する必要を回避するために、@TestExecutionListeners の mergeMode 属性を MergeMode.MERGE_WITH_DEFAULTS に設定できます。MERGE_WITH_DEFAULTS は、ローカルで宣言されたリスナーをデフォルトのリスナーとマージする必要があることを示します。マージアルゴリズムにより、TestExecutionListener 実装のオーダーに従って、重複がリストから削除され、マージされたリスナーの結果セットが AnnotationAwareOrderComparator のセマンティクスに従ってソートされます。リスナーが Ordered を実装するか、@Order のアノテーションが付けられている場合、リスナーがデフォルトとマージされる位置に影響を与える可能性があります。それ以外の場合、ローカルで宣言されたリスナーは、マージ時にデフォルトのリスナーのリストに追加されます。

例:前の例の MyCustomTestExecutionListener クラスが order 値(たとえば 500)を ServletTestExecutionListener (たまたま 1000)の順序よりも小さくなるように構成した場合、MyCustomTestExecutionListener は自動的にデフォルトのリストにマージされます。ServletTestExecutionListener の前面、および前の例は次のように置き換えることができます。

Java
@ContextConfiguration
@TestExecutionListeners(
    listeners = MyCustomTestExecutionListener.class,
    mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
    // class body...
}
Kotlin
@ContextConfiguration
@TestExecutionListeners(
        listeners = [MyCustomTestExecutionListener::class],
        mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
    // class body...
}

3.5.4. テスト実行イベント

Spring Framework 5.2 で導入された EventPublishingTestExecutionListener は、カスタム TestExecutionListener を実装するための代替アプローチを提供します。テストの ApplicationContext のコンポーネントは、EventPublishingTestExecutionListener によって発行された次のイベントをリッスンできます。各イベントは、TestExecutionListener API のメソッドに対応しています。

  • BeforeTestClassEvent

  • PrepareTestInstanceEvent

  • BeforeTestMethodEvent

  • BeforeTestExecutionEvent

  • AfterTestExecutionEvent

  • AfterTestMethodEvent

  • AfterTestClassEvent

これらのイベントは、ApplicationContext が既にロードされている場合にのみ公開されます。

これらのイベントは、モック Bean のリセットやテスト実行のトレースなど、さまざまな理由で消費される場合があります。カスタム TestExecutionListener を実装するのではなく、テスト実行イベントを消費する利点の 1 つは、テスト ApplicationContext に登録された Spring Bean がテスト実行イベントを消費できること、およびそのような Bean が依存関係注入および ApplicationContext の他の機能から直接恩恵を受ける可能性があることです。対照的に、TestExecutionListener は ApplicationContext の Bean ではありません。

テスト実行イベントをリッスンするために、Spring Bean は org.springframework.context.ApplicationListener インターフェースの実装を選択できます。または、リスナーメソッドに @EventListener アノテーションを付けて、上記の特定のイベントタイプの 1 つをリッスンするように構成できます(アノテーションベースのイベントリスナーを参照)。このアプローチの人気により、Spring は次の専用 @EventListener アノテーションを提供して、テスト実行イベントリスナーの登録を簡素化します。これらのアノテーションは、org.springframework.test.context.event.annotation パッケージにあります。

  • @BeforeTestClass

  • @PrepareTestInstance

  • @BeforeTestMethod

  • @BeforeTestExecution

  • @AfterTestExecution

  • @AfterTestMethod

  • @AfterTestClass

例外処理

デフォルトでは、テスト実行イベントリスナーがイベントの消費中に例外をスローすると、その例外は使用中の基礎となるテストフレームワーク(JUnit や TestNG など)に伝播します。例: BeforeTestMethodEvent の消費により例外が発生した場合、対応するテストメソッドは例外の結果として失敗します。対照的に、非同期のテスト実行イベントリスナーが例外をスローする場合、例外は基礎となるテストフレームワークに伝播しません。非同期例外処理の詳細については、@EventListener のクラスレベルの javadoc を参照してください。

非同期リスナー

特定のテスト実行イベントリスナーでイベントを非同期的に処理する場合は、Spring の通常の @Async サポートを使用できます。詳細については、@EventListener のクラスレベルの javadoc を参照してください。

3.5.5. コンテキスト管理

各 TestContext は、担当するテストインスタンスのコンテキスト管理とキャッシュサポートを提供します。テストインスタンスは、構成された ApplicationContext へのアクセスを自動的に受け取りません。ただし、テストクラスが ApplicationContextAware インターフェースを実装する場合、ApplicationContext への参照がテストインスタンスに提供されます。AbstractJUnit4SpringContextTests および AbstractTestNGSpringContextTests は ApplicationContextAware を実装しているため、ApplicationContext へのアクセスを自動的に提供することに注意してください。

@Autowired ApplicationContext

ApplicationContextAware インターフェースを実装する代わりに、次の例に示すように、フィールドまたは setter メソッドの @Autowired アノテーションを使用して、テストクラスのアプリケーションコンテキストを注入できます。

Java
@SpringJUnitConfig
class MyTest {

    @Autowired (1)
    ApplicationContext applicationContext;

    // class body...
}
1ApplicationContext の注入。
Kotlin
@SpringJUnitConfig
class MyTest {

    @Autowired (1)
    lateinit var applicationContext: ApplicationContext

    // class body...
}
1ApplicationContext の注入。

同様に、テストが WebApplicationContext をロードするように構成されている場合、次のようにテストに Web アプリケーションコンテキストを挿入できます。

Java
@SpringJUnitWebConfig (1)
class MyWebAppTest {

    @Autowired (2)
    WebApplicationContext wac;

    // class body...
}
1WebApplicationContext の構成。
2WebApplicationContext の注入。
Kotlin
@SpringJUnitWebConfig (1)
class MyWebAppTest {

    @Autowired (2)
    lateinit var wac: WebApplicationContext
    // class body...
}
1WebApplicationContext の構成。
2WebApplicationContext の注入。

@Autowired を使用した依存性注入は、デフォルトで構成されている DependencyInjectionTestExecutionListener によって提供されます(テストフィクスチャの依存性注入を参照)。

TestContext フレームワークを使用するテストクラスは、特定のクラスを継承したり、特定のインターフェースを実装してアプリケーションコンテキストを構成したりする必要はありません。代わりに、構成はクラスレベルで @ContextConfiguration アノテーションを宣言することで実現されます。テストクラスがアプリケーションコンテキストリソースの場所またはコンポーネントクラスを明示的に宣言しない場合、構成された ContextLoader は、デフォルトの場所またはデフォルトの構成クラスからコンテキストをロードする方法を決定します。コンテキストリソースの場所とコンポーネントクラスに加えて、アプリケーションコンテキストは、アプリケーションコンテキスト初期化子を通じて構成することもできます。

以下のセクションでは、Spring の @ContextConfiguration アノテーションを使用して、XML 構成ファイル、Groovy スクリプト、コンポーネントクラス(通常 @Configuration クラス)、またはコンテキスト初期化子を使用して、テスト ApplicationContext を構成する方法について説明します。または、高度なユースケース用に独自のカスタム SmartContextLoader を実装および構成できます。

XML リソースを使用したコンテキスト設定

XML 構成ファイルを使用して ApplicationContext をテスト用にロードするには、テストクラスに @ContextConfiguration のアノテーションを付け、XML 構成メタデータのリソースの場所を含む配列で locations 属性を構成します。プレーンパスまたは相対パス(たとえば、context.xml)は、テストクラスが定義されているパッケージに相対的なクラスパスリソースとして扱われます。スラッシュで始まるパスは、絶対クラスパスの場所として扱われます(たとえば、/org/example/config.xml)。リソース URL を表すパス(つまり、接頭辞 classpath:file:http: など)がそのまま使用されます

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
    // class body...
}
1 ロケーション属性を XML ファイルのリストに設定します。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
    // class body...
}
1 ロケーション属性を XML ファイルのリストに設定します。

@ContextConfiguration は、標準 Java value 属性を介して locations 属性のエイリアスをサポートします。@ContextConfiguration で追加の属性を宣言する必要がない場合は、次の例に示す短縮形を使用して、locations 属性名の宣言を省略し、リソースの場所を宣言できます。

Java
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
    // class body...
}
1location 属性を使用せずに XML ファイルを指定します。
Kotlin
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
    // class body...
}
1location 属性を使用せずに XML ファイルを指定します。

@ContextConfiguration アノテーションから locations 属性と value 属性の両方を省略すると、TestContext フレームワークはデフォルトの XML リソースの場所を検出しようとします。具体的には、GenericXmlContextLoader および GenericXmlWebContextLoader は、テストクラスの名前に基づいてデフォルトの場所を検出します。クラスの名前が com.example.MyTestGenericXmlContextLoader の場合、"classpath:com/example/MyTest-context.xml" からアプリケーションコンテキストをロードします。次の例は、その方法を示しています。

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
1 デフォルトの場所から構成をロードしています。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
1 デフォルトの場所から構成をロードしています。
Groovy スクリプトを使用したコンテキスト設定

Groovy Bean 定義 DSL を使用する Groovy スクリプトを使用して、テスト用に ApplicationContext をロードするには、@ContextConfiguration でテストクラスにアノテーションを付け、Groovy スクリプトのリソースの場所を含む配列で locations または value 属性を構成します。Groovy スクリプトのリソースルックアップセマンティクスは、XML 構成ファイルについて説明したものと同じです。

Groovy スクリプトサポートの有効化
Groovy がクラスパス上にある場合、Groovy スクリプトを使用して Spring に ApplicationContext をロードするためのサポート TestContext フレームワークは自動的に有効になります。

次の例は、Groovy 構成ファイルを指定する方法を示しています。

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) (1)
class MyTest {
    // class body...
}
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration("/AppConfig.groovy", "/TestConfig.Groovy") (1)
class MyTest {
    // class body...
}
1Groovy 構成ファイルの場所を指定します。

@ContextConfiguration アノテーションから locations 属性と value 属性の両方を省略すると、TestContext フレームワークはデフォルトの Groovy スクリプトを検出しようとします。具体的には、GenericGroovyXmlContextLoader および GenericGroovyXmlWebContextLoader は、テストクラスの名前に基づいてデフォルトの場所を検出します。クラスの名前が com.example.MyTest の場合、Groovy コンテキストローダーは "classpath:com/example/MyTestContext.groovy" からアプリケーションコンテキストをロードします。次の例は、デフォルトの使用方法を示しています。

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
1 デフォルトの場所から構成をロードしています。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
1 デフォルトの場所から構成をロードしています。
XML 構成と Groovy スクリプトを同時に宣言する

@ContextConfiguration の locations または value 属性を使用して、XML 構成ファイルと Groovy スクリプトの両方を同時に宣言できます。構成されたリソースの場所へのパスが .xml で終わる場合、XmlBeanDefinitionReader を使用してロードされます。それ以外の場合は、GroovyBeanDefinitionReader を使用してロードされます。

次のリストは、統合テストで両方を組み合わせる方法を示しています。

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "/app-config.xml" and "/TestConfig.groovy"
@ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" })
class MyTest {
    // class body...
}
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "/app-config.xml" and "/TestConfig.groovy"
@ContextConfiguration("/app-config.xml", "/TestConfig.groovy")
class MyTest {
    // class body...
}
コンポーネントクラスを使用したコンテキスト設定

コンポーネントクラス(Java ベースのコンテナー構成を参照)を使用して ApplicationContext をテスト用にロードするには、テストクラスに @ContextConfiguration のアノテーションを付け、コンポーネントクラスへの参照を含む配列で classes 属性を構成できます。次の例は、その方法を示しています。

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) (1)
class MyTest {
    // class body...
}
1 コンポーネントクラスの指定。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = [AppConfig::class, TestConfig::class]) (1)
class MyTest {
    // class body...
}
1 コンポーネントクラスの指定。
コンポーネントクラス

「コンポーネントクラス」という用語は、次のいずれかを指します。

  • @Configuration アノテーションが付けられたクラス。

  • コンポーネント(つまり、@Component@Service@Repository、またはその他のステレオタイプアノテーションが付けられたクラス)。

  • javax.inject アノテーションが付けられた JSR-330 準拠のクラス。

  • @Bean メソッドを含むクラス。

  • Spring コンポーネント(つまり、ApplicationContext の Spring Bean)として登録することを目的とするその他のクラス。潜在的に Spring アノテーションを使用せずに単一のコンストラクターの自動オートワイヤーを利用します。

@Bean Lite モードの説明に特に注意を払いながら、コンポーネントクラスの構成とセマンティクスに関する詳細については、@Configuration(Javadoc) および @Bean(Javadoc) の javadoc を参照してください。

@ContextConfiguration アノテーションから classes 属性を省略すると、TestContext フレームワークはデフォルトの構成クラスの存在を検出しようとします。具体的には、AnnotationConfigContextLoader および AnnotationConfigWebContextLoader は、@Configuration(Javadoc) javadoc で指定されているように、構成クラス実装の要件を満たすテストクラスの static ネストクラスをすべて検出します。構成クラスの名前は任意です。さらに、テストクラスには、必要に応じて複数の static ネストされた構成クラスを含めることができます。次の例では、OrderServiceTest クラスは、テストクラスの ApplicationContext をロードするために自動的に使用される Config という名前の static ネストされた構成クラスを宣言します。

Java
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the
// static nested Config class
class OrderServiceTest {

    @Configuration
    static class Config {

        // this bean will be injected into the OrderServiceTest class
        @Bean
        OrderService orderService() {
            OrderService orderService = new OrderServiceImpl();
            // set properties, etc.
            return orderService;
        }
    }

    @Autowired
    OrderService orderService;

    @Test
    void testOrderService() {
        // test the orderService
    }

}
1 ネストされた Config クラスから構成情報をロードします。
Kotlin
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the nested Config class
class OrderServiceTest {

    @Autowired
    lateinit var orderService: OrderService

    @Configuration
    class Config {

        // this bean will be injected into the OrderServiceTest class
        @Bean
        fun orderService(): OrderService {
            // set properties, etc.
            return OrderServiceImpl()
        }
    }

    @Test
    fun testOrderService() {
        // test the orderService
    }
}
1 ネストされた Config クラスから構成情報をロードします。
XML、Groovy スクリプト、およびコンポーネントクラスの混合

テスト用に ApplicationContext を構成するには、XML 構成ファイル、Groovy スクリプト、およびコンポーネントクラス(通常は @Configuration クラス)を混在させることが望ましい場合があります。例:本番環境で XML 構成を使用する場合、@Configuration クラスを使用して、テスト用に特定の Spring 管理コンポーネントを構成したり、その逆を行うことができます。

さらに、一部のサードパーティフレームワーク(Spring Boot など)は、異なる種類のリソース(たとえば、XML 構成ファイル、Groovy スクリプト、@Configuration クラス)から ApplicationContext を同時に読み込むためのファーストクラスのサポートを提供します。歴史的に、Spring Framework は標準デプロイに対してこれをサポートしていませんでした。その結果、Spring Framework が spring-test モジュールで提供する SmartContextLoader 実装のほとんどは、各テストコンテキストに対して 1 つのリソースタイプのみをサポートします。ただし、これは両方を使用できないという意味ではありません。一般規則の 1 つの例外は、GenericGroovyXmlContextLoader と GenericGroovyXmlWebContextLoader が XML 構成ファイルと Groovy スクリプトの両方を同時にサポートすることです。さらに、サードパーティのフレームワークは、locations と classes から @ContextConfiguration の両方の宣言をサポートすることを選択できます。また、TestContext フレームワークの標準テストサポートでは、次のオプションがあります。

リソースの場所(XML や Groovy など)と @Configuration クラスを使用してテストを構成する場合は、一方をエントリポイントとして選択し、もう一方を含めるかインポートする必要があります。例:XML または Groovy スクリプトでは、コンポーネントスキャンを使用するか、通常の Spring Bean として定義することにより、@Configuration クラスを含めることができます。一方、@Configuration クラスでは、@ImportResource を使用して XML 構成ファイルまたは Groovy スクリプトをインポートできます。この動作は、本番環境でアプリケーションを構成する方法と意味的に同等です。本番構成では、XML または Groovy リソースの場所のセット、または本番 ApplicationContext がロードされる @Configuration クラスのセットのいずれかを定義しますが、他のタイプの構成を含めたりインポートしたりする自由。

コンテキスト初期化子を使用したコンテキスト構成

コンテキスト初期化子を使用してテスト用に ApplicationContext を構成するには、@ContextConfiguration でテストクラスにアノテーションを付け、ApplicationContextInitializer を実装するクラスへの参照を含む配列で initializers 属性を構成します。宣言されたコンテキスト初期化子は、テスト用にロードされる ConfigurableApplicationContext を初期化するために使用されます。宣言された各イニシャライザによってサポートされる具体的な ConfigurableApplicationContext タイプは、使用中の SmartContextLoader によって作成された ApplicationContext のタイプ(通常は GenericApplicationContext)と互換性がある必要があることに注意してください。さらに、初期化子が呼び出される順序は、Spring の Ordered インターフェースを実装するか、Spring の @Order アノテーションまたは標準 @Priority アノテーションが付けられているかによって異なります。次の例は、初期化子の使用方法を示しています。

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
    classes = TestConfig.class,
    initializers = TestAppCtxInitializer.class) (1)
class MyTest {
    // class body...
}
1 構成クラスと初期化子を使用して構成を指定します。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
        classes = [TestConfig::class],
        initializers = [TestAppCtxInitializer::class]) (1)
class MyTest {
    // class body...
}
1 構成クラスと初期化子を使用して構成を指定します。

また、XML 構成ファイル、Groovy スクリプト、または @ContextConfiguration のコンポーネントクラスの宣言を完全に省略し、代わりに ApplicationContextInitializer クラスのみを宣言することができます。ApplicationContextInitializer クラスは、XML ファイルから Bean 定義をプログラムでロードしたり、構成クラス。次の例は、その方法を示しています。

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class) (1)
class MyTest {
    // class body...
}
1 初期化子のみを使用して構成を指定します。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = [EntireAppInitializer::class]) (1)
class MyTest {
    // class body...
}
1 初期化子のみを使用して構成を指定します。
コンテキスト設定の継承

@ContextConfiguration は、ブール型 inheritLocations および inheritInitializers 属性をサポートします。これらの属性は、リソースの場所またはコンポーネントクラス、およびスーパークラスによって宣言されたコンテキスト初期化子を継承する必要があるかどうかを示します。両方のフラグのデフォルト値は true です。つまり、テストクラスは、リソースの場所またはコンポーネントクラス、およびスーパークラスによって宣言されたコンテキスト初期化子を継承します。具体的には、テストクラスのリソースの場所またはコンポーネントクラスは、スーパークラスによって宣言されたリソースの場所またはアノテーション付きクラスのリストに追加されます。同様に、特定のテストクラスの初期化子は、テストスーパークラスによって定義された初期化子のセットに追加されます。サブクラスには、リソースの場所、コンポーネントクラス、またはコンテキスト初期化子を継承するオプションがあります。

@ContextConfiguration の inheritLocations または inheritInitializers 属性が false に設定されている場合、テストクラスシャドウのリソースの場所またはコンポーネントクラスとコンテキスト初期化子はそれぞれ、スーパークラスによって定義された構成を効果的に置き換えます。

XML リソースの場所を使用する次の例では、ExtendedTest の ApplicationContext が base-config.xml および extended-config.xml からこの順序でロードされます。extended-config.xml で定義された Bean は、base-config.xml で定義された Bean をオーバーライド(つまり、置換)できます。次の例は、あるクラスが別のクラスを継承し、独自の構成ファイルとスーパークラスの構成ファイルの両方を使用する方法を示しています。

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest extends BaseTest {
    // class body...
}
1 スーパークラスで定義された構成ファイル。
2 サブクラスで定義された構成ファイル。
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
open class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest : BaseTest() {
    // class body...
}
1 スーパークラスで定義された構成ファイル。
2 サブクラスで定義された構成ファイル。

同様に、コンポーネントクラスを使用する次の例では、ExtendedTest の ApplicationContext が BaseConfig クラスと ExtendedConfig クラスからこの順序でロードされます。ExtendedConfig で定義された Bean は、BaseConfig で定義された Bean をオーバーライド(つまり、置換)できます。次の例は、あるクラスが別のクラスを継承し、独自の構成クラスとスーパークラスの構成クラスの両方を使用する方法を示しています。

Java
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig.class) (1)
class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class) (2)
class ExtendedTest extends BaseTest {
    // class body...
}
1 スーパークラスで定義された構成クラス。
2 サブクラスで定義された構成クラス。
Kotlin
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig::class) (1)
open class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig::class) (2)
class ExtendedTest : BaseTest() {
    // class body...
}
1 スーパークラスで定義された構成クラス。
2 サブクラスで定義された構成クラス。

コンテキスト初期化子を使用する次の例では、ExtendedTest の ApplicationContext は BaseInitializer および ExtendedInitializer を使用して初期化されます。ただし、イニシャライザが呼び出される順序は、Spring の Ordered インターフェースを実装するか、Spring の @Order アノテーションまたは標準 @Priority アノテーションが付けられているかによって異なります。次の例は、あるクラスが別のクラスを継承し、独自の初期化子とスーパークラスの初期化子の両方を使用する方法を示しています。

Java
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = BaseInitializer.class) (1)
class BaseTest {
    // class body...
}

// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class) (2)
class ExtendedTest extends BaseTest {
    // class body...
}
1 スーパークラスで定義された初期化子。
2 サブクラスで定義された初期化子。
Kotlin
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = [BaseInitializer::class]) (1)
open class BaseTest {
    // class body...
}

// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = [ExtendedInitializer::class]) (2)
class ExtendedTest : BaseTest() {
    // class body...
}
1 スーパークラスで定義された初期化子。
2 サブクラスで定義された初期化子。
環境プロファイルを使用したコンテキスト設定

Spring Framework には、環境とプロファイル(別名「Bean 定義プロファイル」)の概念に対するファーストクラスのサポートがあり、さまざまなテストシナリオで特定の Bean 定義プロファイルをアクティブにするように統合テストを構成できます。これは、テストクラスに @ActiveProfiles アノテーションを付けて、テスト用に ApplicationContext をロードするときにアクティブ化する必要があるプロファイルのリストを提供することで実現されます。

@ActiveProfiles は SmartContextLoader SPI のどの実装でも使用できますが、@ActiveProfiles は古い ContextLoader SPI の実装ではサポートされていません。

XML 構成と @Configuration クラスを使用した 2 つの例を検討してください。

<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <bean id="transferService"
            class="com.bank.service.internal.DefaultTransferService">
        <constructor-arg ref="accountRepository"/>
        <constructor-arg ref="feePolicy"/>
    </bean>

    <bean id="accountRepository"
            class="com.bank.repository.internal.JdbcAccountRepository">
        <constructor-arg ref="dataSource"/>
    </bean>

    <bean id="feePolicy"
        class="com.bank.service.internal.ZeroFeePolicy"/>

    <beans profile="dev">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script
                location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>

    <beans profile="default">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
        </jdbc:embedded-database>
    </beans>

</beans>
Java
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    lateinit var transferService: TransferService

    @Test
    fun testTransferService() {
        // test the transferService
    }
}

TransferServiceTest が実行されると、その ApplicationContext はクラスパスのルートにある app-config.xml 構成ファイルからロードされます。app-config.xml を調べると、accountRepository Bean が dataSource Bean に依存していることがわかります。ただし、dataSource はトップレベル Bean として定義されていません。代わりに、dataSource は、production プロファイル、dev プロファイル、および default プロファイルで 3 回定義されます。

TransferServiceTest に @ActiveProfiles("dev") のアノテーションを付けることにより、Spring TestContext フレームワークに、{"dev"} に設定されたアクティブなプロファイルで ApplicationContext をロードするよう指示します。その結果、組み込みデータベースが作成され、テストデータが入力され、accountRepository Bean が開発 DataSource への参照で接続されます。これはおそらく統合テストで必要なものです。

Bean を default プロファイルに割り当てると便利な場合があります。デフォルトプロファイル内の Bean は、他のプロファイルが特にアクティブ化されていない場合にのみ含まれます。これを使用して、アプリケーションのデフォルト状態で使用される「フォールバック」Bean を定義できます。例: dev および production プロファイルのデータソースを明示的に提供できますが、どちらもアクティブでない場合は、メモリ内データソースをデフォルトとして定義できます。

次のコードリストは、XML の代わりに @Configuration クラスを使用して同じ構成および統合テストを実装する方法を示しています。

Java
@Configuration
@Profile("dev")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
Kotlin
@Configuration
@Profile("dev")
class StandaloneDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build()
    }
}
Java
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
Kotlin
@Configuration
@Profile("production")
class JndiDataConfig {

    @Bean(destroyMethod = "")
    fun dataSource(): DataSource {
        val ctx = InitialContext()
        return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
    }
}
Java
@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}
Kotlin
@Configuration
@Profile("default")
class DefaultDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .build()
    }
}
Java
@Configuration
public class TransferServiceConfig {

    @Autowired DataSource dataSource;

    @Bean
    public TransferService transferService() {
        return new DefaultTransferService(accountRepository(), feePolicy());
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public FeePolicy feePolicy() {
        return new ZeroFeePolicy();
    }
}
Kotlin
@Configuration
class TransferServiceConfig {

    @Autowired
    lateinit var dataSource: DataSource

    @Bean
    fun transferService(): TransferService {
        return DefaultTransferService(accountRepository(), feePolicy())
    }

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }

    @Bean
    fun feePolicy(): FeePolicy {
        return ZeroFeePolicy()
    }
}
Java
@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}
Kotlin
@SpringJUnitConfig(
        TransferServiceConfig::class,
        StandaloneDataConfig::class,
        JndiDataConfig::class,
        DefaultDataConfig::class)
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    lateinit var transferService: TransferService

    @Test
    fun testTransferService() {
        // test the transferService
    }
}

このバリエーションでは、XML 構成を 4 つの独立した @Configuration クラスに分割しました。

  • TransferServiceConfig@Autowired を使用して、依存性注入により dataSource を取得します。

  • StandaloneDataConfig: 開発者テストに適した組み込みデータベースの dataSource を定義します。

  • JndiDataConfig: 本番環境で JNDI から取得される dataSource を定義します。

  • DefaultDataConfig: アクティブなプロファイルがない場合に、デフォルトの組み込みデータベースの dataSource を定義します。

XML ベースの構成例と同様に、TransferServiceTest に @ActiveProfiles("dev") のアノテーションを付けますが、今回は @ContextConfiguration アノテーションを使用して 4 つの構成クラスすべてを指定します。テストクラスの本体自体は完全に変更されていません。

多くの場合、特定のプロジェクト内の複数のテストクラスで単一のプロファイルセットが使用されます。@ActiveProfiles アノテーションの重複した宣言を避けるために、ベースクラスで @ActiveProfiles を 1 回宣言することができ、サブクラスはベースクラスから @ActiveProfiles 構成を自動的に継承します。次の例では、@ActiveProfiles の宣言(およびその他のアノテーション)が抽象スーパークラス AbstractIntegrationTest に移動されています。

Java
@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
Kotlin
@SpringJUnitConfig(
        TransferServiceConfig::class,
        StandaloneDataConfig::class,
        JndiDataConfig::class,
        DefaultDataConfig::class)
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
Java
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}
Kotlin
// "dev" profile inherited from superclass
class TransferServiceTest : AbstractIntegrationTest() {

    @Autowired
    lateinit var transferService: TransferService

    @Test
    fun testTransferService() {
        // test the transferService
    }
}

@ActiveProfiles は、次の例に示すように、アクティブプロファイルの継承を無効にするために使用できる inheritProfiles 属性もサポートしています。

Java
// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
    // test body
}
Kotlin
// "dev" profile overridden with "production"
@ActiveProfiles("production", inheritProfiles = false)
class ProductionTransferServiceTest : AbstractIntegrationTest() {
    // test body
}

さらに、テストのアクティブなプロファイルを、宣言的にではなくプログラムによって解決することが必要になる場合があります。たとえば、次のものに基づいています。

  • 現在のオペレーティングシステム。

  • テストが継続的インテグレーションビルドサーバーで実行されているかどうか。

  • 特定の環境変数の存在。

  • カスタムクラスレベルのアノテーションの存在。

  • その他の懸念。

アクティブな Bean 定義プロファイルをプログラムで解決するために、カスタム ActiveProfilesResolver を実装し、@ActiveProfiles の resolver 属性を使用してそれを登録できます。詳細については、対応する javadoc を参照してください。次の例は、カスタム OperatingSystemActiveProfilesResolver を実装および登録する方法を示しています。

Java
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
        resolver = OperatingSystemActiveProfilesResolver.class,
        inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
    // test body
}
Kotlin
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
        resolver = OperatingSystemActiveProfilesResolver::class,
        inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
    // test body
}
Java
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {

    @Override
    public String[] resolve(Class<?> testClass) {
        String profile = ...;
        // determine the value of profile based on the operating system
        return new String[] {profile};
    }
}
Kotlin
class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver {

    override fun resolve(testClass: Class<*>): Array<String> {
        val profile: String = ...
        // determine the value of profile based on the operating system
        return arrayOf(profile)
    }
}
テストプロパティソースを使用したコンテキスト構成

Spring Framework は、プロパティソースの階層を持つ環境の概念に対するファーストクラスのサポートを備えており、テスト固有のプロパティソースを使用して統合テストを構成できます。@Configuration クラスで使用される @PropertySource アノテーションとは対照的に、テストクラスで @TestPropertySource アノテーションを宣言して、テストプロパティファイルまたはインラインプロパティのリソースの場所を宣言できます。これらのテストプロパティソースは、アノテーション付き統合テスト用に読み込まれた ApplicationContext の Environment の PropertySources のセットに追加されます。

@TestPropertySource は SmartContextLoader SPI のどの実装でも使用できますが、@TestPropertySource は古い ContextLoader SPI の実装ではサポートされていません。

SmartContextLoader の実装は、MergedContextConfiguration の getPropertySourceLocations() および getPropertySourceProperties() メソッドを介して、マージされたテストプロパティソース値にアクセスします。

テストプロパティソースの宣言

@TestPropertySource の locations または value 属性を使用して、テストプロパティファイルを構成できます。

"classpath:/com/example/test.properties" や "file:///path/to/file.xml" など、従来のプロパティファイル形式と XML ベースのプロパティファイル形式の両方がサポートされています。

各パスは Spring Resource として解釈されます。プレーンパス("test.properties" など)は、テストクラスが定義されているパッケージに関連するクラスパスリソースとして扱われます。スラッシュで始まるパスは、絶対クラスパスリソースとして扱われます(例: "/org/example/test.xml")。URL を参照するパス(たとえば、接頭辞 classpath:file: または http: のパス)は、指定されたリソースプロトコルを使用してロードされます。リソースの場所のワイルドカード(*/.properties など)は許可されません。各場所は、正確に 1 つの .properties または .xml リソースに評価される必要があります。

次の例では、テストプロパティファイルを使用しています。

Java
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 絶対パスでプロパティファイルを指定します。
Kotlin
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
1 絶対パスでプロパティファイルを指定します。

次の例に示すように、@TestPropertySource の properties 属性を使用して、キーと値のペアの形式でインラインプロパティを構成できます。すべてのキーと値のペアは、最高の優先順位を持つ単一のテスト PropertySource として、包含 Environment に追加されます。

キーと値のペアでサポートされている構文は、Java プロパティファイルのエントリに定義されている構文と同じです。

  • key=value

  • key:value

  • key value

次の例では、2 つのインラインプロパティを設定します。

Java
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) (1)
class MyIntegrationTests {
    // class body...
}
1 キーと値の構文の 2 つのバリエーションを使用して 2 つのプロパティを設定します。
Kotlin
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) (1)
class MyIntegrationTests {
    // class body...
}
1 キーと値の構文の 2 つのバリエーションを使用して 2 つのプロパティを設定します。

Spring Framework 5.2, 以降、@TestPropertySource は繰り返し可能なアノテーションとして使用できます。つまり、後の @TestPropertySource アノテーションの locations と properties が以前の @TestPropertySource アノテーションの @TestPropertySource をオーバーライドして、単一のテストクラスで @TestPropertySource の複数の宣言を持つことができます。

さらに、それぞれ @TestPropertySource でメタアノテーションが付けられた複数の複合アノテーションをテストクラスで宣言できます。これらの @TestPropertySource 宣言はすべて、テストプロパティソースに貢献します。

直接存在する @TestPropertySource アノテーションは、常にメタ存在する @TestPropertySource アノテーションよりも優先されます。つまり、直接存在する @TestPropertySource アノテーションからの locations および properties は、メタアノテーションとして使用される @TestPropertySource アノテーションからの locations および properties をオーバーライドします。

デフォルトのプロパティファイルの検出

@TestPropertySource が空のアノテーションとして宣言されている場合(つまり、locations または properties 属性の明示的な値がない場合)、アノテーションを宣言したクラスに関連するデフォルトのプロパティファイルを検出しようとします。例:アノテーション付きテストクラスが com.example.MyTest の場合、対応するデフォルトプロパティファイルは classpath:com/example/MyTest.properties です。デフォルトを検出できない場合、IllegalStateException がスローされます。

優先順位

テストプロパティは、オペレーティングシステムの環境、Java システムプロパティ、または @PropertySource を使用してプログラムによってアプリケーションによって宣言的に追加されたプロパティソースで定義されたものよりも優先されます。テストプロパティを使用して、システムおよびアプリケーションプロパティソースからロードされたプロパティを選択的にオーバーライドできます。さらに、インライン化されたプロパティは、リソースの場所から読み込まれたプロパティよりも優先されます。ただし、@DynamicPropertySource を介して登録されたプロパティは、@TestPropertySource を介してロードされたプロパティよりも優先されることに注意してください。

次の例では、timezone プロパティと port プロパティ、および "/test.properties" で定義されたプロパティは、システムおよびアプリケーションプロパティソースで定義された同じ名前のプロパティをオーバーライドします。さらに、"/test.properties" ファイルが timezone および port プロパティのエントリを定義する場合、それらは properties 属性を使用して宣言されたインラインプロパティによってオーバーライドされます。次の例は、ファイルとインラインの両方でプロパティを指定する方法を示しています。

Java
@ContextConfiguration
@TestPropertySource(
    locations = "/test.properties",
    properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
    // class body...
}
Kotlin
@ContextConfiguration
@TestPropertySource("/test.properties",
        properties = ["timezone = GMT", "port: 4242"]
)
class MyIntegrationTests {
    // class body...
}
テストプロパティソースの継承とオーバーライド

@TestPropertySource は、スーパークラスによって宣言されたプロパティファイルとインラインプロパティのリソースの場所を継承する必要があるかどうかを示すブール inheritLocations および inheritProperties 属性をサポートしています。両方のフラグのデフォルト値は true です。つまり、テストクラスは、スーパークラスによって宣言された場所とインラインプロパティを継承します。具体的には、テストクラスの場所とインラインプロパティは、スーパークラスによって宣言された場所とインラインプロパティに追加されます。サブクラスには、場所とインラインプロパティを継承するオプションがあります。後で表示されるプロパティは、以前に表示される同じ名前のプロパティをシャドウ(つまり、オーバーライド)することに注意してください。さらに、前述の優先規則は、継承されたテストプロパティソースにも適用されます。

@TestPropertySource の inheritLocations または inheritProperties 属性が false に設定されている場合、テストクラスシャドウの場所またはインラインプロパティはそれぞれ、スーパークラスによって定義された構成を効果的に置き換えます。

次の例では、BaseTest の ApplicationContext は、base.properties ファイルのみをテストプロパティソースとして使用してロードされます。対照的に、ExtendedTest の ApplicationContext は、base.properties および extended.properties ファイルをテストプロパティソースの場所として使用してロードされます。次の例は、properties ファイルを使用して、サブクラスとそのスーパークラスの両方でプロパティを定義する方法を示しています。

Java
@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {
    // ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
    // ...
}
Kotlin
@TestPropertySource("base.properties")
@ContextConfiguration
open class BaseTest {
    // ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest : BaseTest() {
    // ...
}

次の例では、インライン key1 プロパティのみを使用して、BaseTest の ApplicationContext がロードされます。対照的に、ExtendedTest の ApplicationContext は、インラインの key1 および key2 プロパティを使用してロードされます。次の例は、インラインプロパティを使用して、サブクラスとそのスーパークラスの両方でプロパティを定義する方法を示しています。

Java
@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
    // ...
}

@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
    // ...
}
Kotlin
@TestPropertySource(properties = ["key1 = value1"])
@ContextConfiguration
open class BaseTest {
    // ...
}

@TestPropertySource(properties = ["key2 = value2"])
@ContextConfiguration
class ExtendedTest : BaseTest() {
    // ...
}
動的プロパティソースを使用したコンテキスト構成

Spring Framework 5.2.5, 以降、TestContext フレームワークは、@DynamicPropertySource アノテーションを介して動的プロパティをサポートします。このアノテーションは、統合テスト用にロードされた ApplicationContext の Environment 内の PropertySources のセットに動的な値を持つプロパティを追加する必要がある統合テストで使用できます。

@DynamicPropertySource アノテーションとそのサポートインフラストラクチャは、当初、テストコンテナー (英語) ベースのテストのプロパティを Spring 統合テストに簡単に公開できるように設計されます。ただし、この機能は、テストの ApplicationContext 外でライフサイクルが維持される外部リソースの任意の形式で使用することもできます。

クラスレベルで適用される @TestPropertySource アノテーションとは対照的に、@DynamicPropertySource は、Environment名前と値のペアを追加するために使用される単一の DynamicPropertyRegistry 引数を受け入れる static メソッドに適用される必要があります。値は動的であり、プロパティが解決されたときにのみ呼び出される Supplier を介して提供されます。通常、メソッド参照は値を提供するために使用されます。次の例では、Testcontainers プロジェクトを使用して Spring ApplicationContext 外の Redis コンテナーを管理しています。管理された Redis コンテナーの IP アドレスとポートは、redis.host および redis.port プロパティを介して、テストの ApplicationContext 内のコンポーネントで利用できるようになります。これらのプロパティは、Spring の Environment 抽象化を介してアクセスするか、たとえば、それぞれ @Value("${redis.host}") および @Value("${redis.port}") を介して、Spring 管理のコンポーネントに直接注入できます。

Java
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {

    @Container
    static RedisContainer redis = new RedisContainer();

    @DynamicPropertySource
    static void redisProperties(DynamicPropertyRegistry registry) {
        registry.add("redis.host", redis::getContainerIpAddress);
        registry.add("redis.port", redis::getMappedPort);
    }

    // tests ...

}
Kotlin
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {

    companion object {

        @Container
        @JvmStatic
        val redis: RedisContainer = RedisContainer()

        @DynamicPropertySource
        @JvmStatic
        fun redisProperties(registry: DynamicPropertyRegistry) {
            registry.add("redis.host", redis::getContainerIpAddress)
            registry.add("redis.port", redis::getMappedPort)
        }
    }

    // tests ...

}
優先順位

動的プロパティは、@TestPropertySource、オペレーティングシステムの環境、Java システムプロパティ、またはアプリケーションによって宣言的に @PropertySource を使用して、またはプログラムによって追加されたプロパティソースからロードされたプロパティよりも優先されます。動的プロパティを使用して、@TestPropertySource、システムプロパティソース、およびアプリケーションプロパティソースを介してロードされたプロパティを選択的にオーバーライドできます。

WebApplicationContext のロード

TestContext フレームワークに標準の ApplicationContext ではなく WebApplicationContext をロードするよう指示するには、それぞれのテストクラスに @WebAppConfiguration でアノテーションを付けます。

テストクラスに @WebAppConfiguration が存在すると、TestContext フレームワーク(TCF)に対して、統合テストのために WebApplicationContext (WAC)をロードする必要があります。バックグラウンドで、TCF は MockServletContext が作成され、テストの WAC に提供されるようにします。デフォルトでは、MockServletContext のベースリソースパスは src/main/webapp に設定されています。これは、JVM のルートに対する相対パス(通常はプロジェクトへのパス)として解釈されます。Maven プロジェクトの Web アプリケーションのディレクトリ構造に精通している場合は、src/main/webapp が WAR のルートのデフォルトの場所であることを知っています。このデフォルトをオーバーライドする必要がある場合は、@WebAppConfiguration アノテーションへの代替パス(たとえば、@WebAppConfiguration("src/test/webapp"))を提供できます。ファイルシステムではなくクラスパスからベースリソースパスを参照する場合は、Spring の classpath: プレフィックスを使用できます。

WebApplicationContext 実装に対する Spring のテストサポートは、標準 ApplicationContext 実装に対するサポートと同等です。WebApplicationContext でテストする場合、@ContextConfiguration を使用して、XML 構成ファイル、Groovy スクリプト、または @Configuration クラスを自由に宣言できます。@ActiveProfiles@TestExecutionListeners@Sql@Rollback など、他のテストアノテーションも自由に使用できます。

このセクションの残りの例は、WebApplicationContext をロードするためのさまざまな構成オプションの一部を示しています。次の例は、TestContext フレームワークの設定よりも規約に対するサポートを示しています。

Java
@ExtendWith(SpringExtension.class)

// defaults to "file:src/main/webapp"
@WebAppConfiguration

// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
    //...
}
Kotlin
@ExtendWith(SpringExtension::class)

// defaults to "file:src/main/webapp"
@WebAppConfiguration

// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
    //...
}

リソースベースパスを指定せずに @WebAppConfiguration でテストクラスにアノテーションを付けると、リソースパスは事実上 file:src/main/webapp にデフォルト設定されます。同様に、リソース locations、コンポーネント classes、またはコンテキスト initializers を指定せずに @ContextConfiguration を宣言すると、Spring は規則(つまり、WacTests クラスまたは静的ネスト @Configuration クラスと同じパッケージ内の WacTests-context.xml)を使用して構成の存在を検出しようとします。

次の例は、@WebAppConfiguration でリソースベースパスを明示的に宣言し、@ContextConfiguration で XML リソースの場所を明示的に宣言する方法を示しています。

Java
@ExtendWith(SpringExtension.class)

// file system resource
@WebAppConfiguration("webapp")

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
    //...
}
Kotlin
@ExtendWith(SpringExtension::class)

// file system resource
@WebAppConfiguration("webapp")

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
    //...
}

ここで注意すべき重要なことは、これらの 2 つのアノテーションを持つパスのセマンティクスが異なることです。デフォルトでは、@WebAppConfiguration リソースパスはファイルシステムベースですが、@ContextConfiguration リソースロケーションはクラスパスベースです。

次の例は、Spring リソースプレフィックスを指定することで、両方のアノテーションのデフォルトのリソースセマンティクスをオーバーライドできることを示しています。

Java
@ExtendWith(SpringExtension.class)

// classpath resource
@WebAppConfiguration("classpath:test-web-resources")

// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
    //...
}
Kotlin
@ExtendWith(SpringExtension::class)

// classpath resource
@WebAppConfiguration("classpath:test-web-resources")

// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
    //...
}

この例のコメントを前の例と比較してください。

Web モックの使用

包括的な Web テストサポートを提供するために、TestContext フレームワークには ServletTestExecutionListener があり、デフォルトで有効になっています。WebApplicationContext に対してテストする場合、この TestExecutionListener は、各テストメソッドの前に Spring Web の RequestContextHolder を使用してデフォルトのスレッドローカル状態を設定し、@WebAppConfiguration で構成されたベースリソースパスに基づいて MockHttpServletRequestMockHttpServletResponse、および ServletWebRequest を作成します。ServletTestExecutionListener は、MockHttpServletResponse および ServletWebRequest がテストインスタンスに挿入できることも保証し、テストが完了すると、スレッドローカル状態をクリーンアップします。

テスト用に WebApplicationContext をロードしたら、たとえば、テストフィクスチャをセットアップしたり、Web コンポーネントを呼び出した後にアサーションを実行したりするために、Web モックと対話する必要がある場合があります。次の例は、どのモックをテストインスタンスに自動接続できるかを示しています。WebApplicationContext と MockServletContext は両方ともテストスイート全体でキャッシュされるのに対し、他のモックは ServletTestExecutionListener によってテストメソッドごとに管理されることに注意してください。

Java
@SpringJUnitWebConfig
class WacTests {

    @Autowired
    WebApplicationContext wac; // cached

    @Autowired
    MockServletContext servletContext; // cached

    @Autowired
    MockHttpSession session;

    @Autowired
    MockHttpServletRequest request;

    @Autowired
    MockHttpServletResponse response;

    @Autowired
    ServletWebRequest webRequest;

    //...
}
Kotlin
@SpringJUnitWebConfig
class WacTests {

    @Autowired
    lateinit var wac: WebApplicationContext // cached

    @Autowired
    lateinit var servletContext: MockServletContext // cached

    @Autowired
    lateinit var session: MockHttpSession

    @Autowired
    lateinit var request: MockHttpServletRequest

    @Autowired
    lateinit var response: MockHttpServletResponse

    @Autowired
    lateinit var webRequest: ServletWebRequest

    //...
}
コンテキストキャッシング

TestContext フレームワークがテスト用に ApplicationContext (または WebApplicationContext)をロードすると、そのコンテキストはキャッシュされ、同じテストスイート内で同じ一意のコンテキスト構成を宣言する後続のすべてのテストで再利用されます。キャッシングの仕組みを理解するには、「一意」と「テストスイート」の意味を理解することが重要です。

ApplicationContext は、ロードに使用される構成パラメーターの組み合わせによって一意に識別できます。構成パラメーターの一意の組み合わせを使用して、コンテキストがキャッシュされるキーを生成します。TestContext フレームワークは、次の構成パラメーターを使用してコンテキストキャッシュキーを構築します。

  • locations ( @ContextConfiguration から)

  • classes ( @ContextConfiguration から)

  • contextInitializerClasses ( @ContextConfiguration から)

  • contextCustomizers (ContextCustomizerFactory から)–これには、@DynamicPropertySource メソッド、および @MockBean や @SpyBean などの Spring Boot のテストサポートからのさまざまな機能が含まれます。

  • contextLoader ( @ContextConfiguration から)

  • parent ( @ContextHierarchy から)

  • activeProfiles ( @ActiveProfiles から)

  • propertySourceLocations ( @TestPropertySource から)

  • propertySourceProperties ( @TestPropertySource から)

  • resourceBasePath ( @WebAppConfiguration から)

例: TestClassA が @ContextConfiguration の locations (または value)属性に {"app-config.xml", "test-config.xml"} を指定する場合、TestContext フレームワークは対応する ApplicationContext をロードし、それらのロケーションのみに基づくキーの static コンテキストキャッシュに格納します。そのため、TestClassB がその場所の {"app-config.xml", "test-config.xml"} も(継承を介して明示的または暗黙的に)定義し、@WebAppConfiguration、異なる ContextLoader、異なるアクティブプロファイル、異なるコンテキスト初期化子、異なるテストプロパティソース、または異なる親コンテキストを定義しない場合、同じ ApplicationContext は両方のテストクラスで共有されます。これは、アプリケーションコンテキストを読み込むためのセットアップコストが 1 回(テストスイートごとに)発生するだけであり、その後のテスト実行がはるかに高速であることを意味します。

テストスイートと分岐プロセス

Spring TestContext フレームワークは、アプリケーションコンテキストを静的キャッシュに格納します。これは、コンテキストが文字通り static 変数に格納されることを意味します。つまり、テストが別々のプロセスで実行される場合、静的キャッシュは各テスト実行の間にクリアされ、キャッシュメカニズムを効果的に無効にします。

キャッシングメカニズムを活用するには、すべてのテストを同じプロセスまたはテストスイート内で実行する必要があります。これは、IDE 内のグループとしてすべてのテストを実行することで実現できます。同様に、Ant、Maven、Gradle などのビルドフレームワークでテストを実行する場合、ビルドフレームワークがテスト間で分岐しないことを確認することが重要です。例:Maven Surefire プラグインの forkMode (Apache) が always または pertest に設定されている場合、TestContext フレームワークはテストクラス間のアプリケーションコンテキストをキャッシュできず、結果としてビルドプロセスの実行が大幅に遅くなります。

コンテキストキャッシュのサイズは、デフォルトの最大サイズである 32 に制限されています。最大サイズに達すると、使用頻度が最も低い(LRU)エビクションポリシーが使用され、古いコンテキストが排除されます。spring.test.context.cache.maxSize という名前の JVM システムプロパティを設定することにより、コマンドラインまたはビルドスクリプトから最大サイズを構成できます。別の方法として、SpringProperties API を使用してプログラムで同じプロパティを設定できます。

特定のテストスイート内に多数のアプリケーションコンテキストをロードすると、スイートの実行に不必要に長い時間がかかる可能性があるため、ロードおよびキャッシュされたコンテキストの数を正確に把握することはしばしば有益です。基盤となるコンテキストキャッシュの統計を表示するには、org.springframework.test.context.cache ロギングカテゴリのログレベルを DEBUG に設定できます。

まれに、テストによってアプリケーションコンテキストが破損し、再読み込みが必要な場合(たとえば、Bean 定義またはアプリケーションオブジェクトの状態を変更することによって)、@DirtiesContext を使用してテストクラスまたはテストメソッドにアノテーションを付けることができます(@DirtiesContext の説明を参照) @DirtiesContext)。これにより、Spring は、同じアプリケーションコンテキストを必要とする次のテストを実行する前に、キャッシュからコンテキストを削除し、アプリケーションコンテキストを再構築します。@DirtiesContext アノテーションのサポートは、デフォルトで有効になっている DirtiesContextBeforeModesTestExecutionListener および DirtiesContextTestExecutionListener によって提供されることに注意してください。

コンテキストの階層

ロードされた Spring ApplicationContext に依存する統合テストを作成する場合、多くの場合、単一のコンテキストに対してテストするだけで十分です。ただし、ApplicationContext インスタンスの階層に対してテストすることが有益であるか、必要でさえある場合があります。例:Spring MVC Web アプリケーションを開発している場合、通常、Spring の ContextLoaderListener によってロードされたルート WebApplicationContext と、Spring の DispatcherServlet によってロードされた子 WebApplicationContext があります。これにより、共有コンポーネントとインフラストラクチャ設定がルートコンテキストで宣言され、Web 固有のコンポーネントによって子コンテキストで消費される親子コンテキスト階層が作成されます。別のユースケースは Spring Batch アプリケーションにあります。Spring Batch アプリケーションでは、多くの場合、共有バッチインフラストラクチャの構成を提供する親コンテキストと、特定のバッチジョブの構成用の子コンテキストがあります。

個々のテストクラスまたはテストクラス階層内で、@ContextHierarchy アノテーションを使用してコンテキスト構成を宣言することにより、コンテキスト階層を使用する統合テストを作成できます。テストクラス階層内の複数のクラスでコンテキスト階層が宣言されている場合、コンテキスト階層の特定の名前付きレベルのコンテキスト構成をマージまたはオーバーライドすることもできます。階層内の特定のレベルの構成をマージする場合、構成リソースタイプ(つまり、XML 構成ファイルまたはコンポーネントクラス)は一貫している必要があります。それ以外の場合、異なるリソースタイプを使用して設定されたコンテキスト階層の異なるレベルを持つことは完全に受け入れられます。

このセクションの残りの JUnit Jupiter ベースの例は、コンテキスト階層の使用を必要とする統合テストの一般的な構成シナリオを示しています。

コンテキスト階層を持つ単一のテストクラス

ControllerIntegrationTests は、ルート WebApplicationContext (TestAppConfig@Configuration クラスを使用してロードされる)とディスパッチャーサーブレット WebApplicationContext (WebConfig@Configuration を使用してロードされる)の 2 つのレベルで構成されるコンテキスト階層を宣言することにより、Spring MVC Web アプリケーションの典型的な統合テストシナリオを表します。クラス)。テストインスタンスに自動接続される WebApplicationContext は、子コンテキスト(つまり、階層内の最下位コンテキスト)のものです。次のリストは、この構成シナリオを示しています。

Java
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = TestAppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {

    @Autowired
    WebApplicationContext wac;

    // ...
}
Kotlin
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextHierarchy(
    ContextConfiguration(classes = [TestAppConfig::class]),
    ContextConfiguration(classes = [WebConfig::class]))
class ControllerIntegrationTests {

    @Autowired
    lateinit var wac: WebApplicationContext

    // ...
}
暗黙的な親コンテキストを持つクラス階層

この例のテストクラスは、テストクラス階層内のコンテキスト階層を定義します。AbstractWebTests は、Spring を使用した Web アプリケーションでルート WebApplicationContext の構成を宣言します。ただし、AbstractWebTests は @ContextHierarchy を宣言しないことに注意してください。AbstractWebTests のサブクラスは、オプションでコンテキスト階層に参加したり、@ContextConfiguration の標準的なセマンティクスに従うことができます。SoapWebServiceTests と RestWebServiceTests は両方とも AbstractWebTests を継承し、@ContextHierarchy を使用してコンテキスト階層を定義します。その結果、3 つのアプリケーションコンテキスト(@ContextConfiguration の宣言ごとに 1 つ)が読み込まれ、AbstractWebTests の構成に基づいて読み込まれたアプリケーションコンテキストが、具象サブクラスに読み込まれた各コンテキストの親コンテキストとして設定されます。次のリストは、この構成シナリオを示しています。

Java
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
public class SoapWebServiceTests extends AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
public class RestWebServiceTests extends AbstractWebTests {}
Kotlin
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
abstract class AbstractWebTests

@ContextHierarchy(ContextConfiguration("/spring/soap-ws-config.xml"))
class SoapWebServiceTests : AbstractWebTests()

@ContextHierarchy(ContextConfiguration("/spring/rest-ws-config.xml"))
class RestWebServiceTests : AbstractWebTests()
コンテキスト階層構成がマージされたクラス階層

この例のクラスは、コンテキスト階層内の特定のレベルの構成をマージするための名前付き階層レベルの使用を示しています。BaseTests は、階層内の 2 つのレベル、parent および child を定義します。ExtendedTests は BaseTests を継承し、@ContextConfiguration の name 属性で宣言された名前が両方とも child であることを確認することにより、child 階層レベルのコンテキスト構成をマージするように Spring TestContext フレームワークに指示します。その結果、/app-config.xml 用、/user-config.xml 用、{"/user-config.xml", "/order-config.xml"} 用の 3 つのアプリケーションコンテキストがロードされます。前の例と同様に、/app-config.xml からロードされたアプリケーションコンテキストは、/user-config.xml および {"/user-config.xml", "/order-config.xml"} からロードされたコンテキストの親コンテキストとして設定されます。次のリストは、この構成シナリオを示しています。

Java
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(name = "child", locations = "/order-config.xml")
)
class ExtendedTests extends BaseTests {}
Kotlin
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
    ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
    ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}

@ContextHierarchy(
    ContextConfiguration(name = "child", locations = ["/order-config.xml"])
)
class ExtendedTests : BaseTests() {}
オーバーライドされたコンテキスト階層構成を持つクラス階層

前の例とは対照的に、この例は、@ContextConfiguration の inheritLocations フラグを false に設定することにより、コンテキスト階層内の特定の名前付きレベルの構成をオーバーライドする方法を示しています。その結果、ExtendedTests のアプリケーションコンテキストは /test-user-config.xml からのみロードされ、/app-config.xml からロードされたコンテキストに親が設定されます。次のリストは、この構成シナリオを示しています。

Java
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(
        name = "child",
        locations = "/test-user-config.xml",
        inheritLocations = false
))
class ExtendedTests extends BaseTests {}
Kotlin
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
    ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
    ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}

@ContextHierarchy(
        ContextConfiguration(
                name = "child",
                locations = ["/test-user-config.xml"],
                inheritLocations = false
        ))
class ExtendedTests : BaseTests() {}
コンテキスト階層内のコンテキストを汚す
コンテキストがコンテキスト階層の一部として構成されているテストで @DirtiesContext を使用する場合、hierarchyMode フラグを使用してコンテキストキャッシュをクリアする方法を制御できます。詳細については、Spring テストアノテーションの @DirtiesContext および @DirtiesContext(Javadoc) javadoc の説明を参照してください。

3.5.6. テストフィクスチャの依存性注入

DependencyInjectionTestExecutionListener (デフォルトで設定)を使用すると、テストインスタンスの依存関係は、@ContextConfiguration または関連するアノテーションで設定したアプリケーションコンテキストの Bean から注入されます。選択するアノテーションと、setter メソッドまたはフィールドに配置するかどうかに応じて、setter インジェクション、フィールドインジェクション、またはその両方を使用できます。JUnit Jupiter を使用している場合は、オプションでコンストラクター注入を使用することもできます(SpringExtension による依存性注入を参照)。Spring のアノテーションベースの注入サポートとの一貫性を保つため、Spring の @Autowired アノテーションまたは JSR-330 の @Inject アノテーションをフィールドおよび setter 注入に使用することもできます。

JUnit Jupiter 以外のテストフレームワークの場合、TestContext フレームワークはテストクラスのインスタンス化に参加しません。コンストラクターに @Autowired または @Inject を使用しても、テストクラスには効果がありません。
フィールドインジェクションは製品コードでは推奨されていませんが、実際にはテストコードではフィールドインジェクションは非常に自然です。違いの理由は、テストクラスを直接インスタンス化しないことです。テストクラスで public コンストラクターまたは setter メソッドを呼び出す必要はありません。

@Autowired は type によるオートワイヤーの実行に使用されるため、同じタイプの Bean 定義が複数ある場合、これらの特定の Bean にこのアプローチを当てにすることはできません。その場合、@Autowired を @Qualifier と組み合わせて使用できます。@Inject を @Named と組み合わせて使用することもできます。あるいは、テストクラスが ApplicationContext にアクセスできる場合は、applicationContext.getBean("titleRepository", TitleRepository.class) の呼び出しを使用して明示的なルックアップを実行できます。

テストインスタンスに依存性注入を適用したくない場合は、フィールドまたは setter メソッドに @Autowired または @Inject でアノテーションを付けないでください。あるいは、@TestExecutionListeners を使用してクラスを明示的に構成し、リスナーのリストから DependencyInjectionTestExecutionListener.class を省略することにより、依存性注入を完全に無効にすることができます。

ゴールセクションで概説されているように、HibernateTitleRepository クラスをテストするシナリオを検討してください。次の 2 つのコードリストは、フィールドでの @Autowired および setter メソッドの使用箇所を示しています。アプリケーションコンテキスト構成は、すべてのサンプルコードリストの後に表示されます。

次のコードリストの依存性注入動作は、JUnit Jupiter に固有のものではありません。同じ DI テクニックは、サポートされているテストフレームワークと組み合わせて使用できます。

次の例では、assertNotNull() などの静的アサーションメソッドを呼び出しますが、呼び出しの先頭に Assertions を付けません。このような場合、この例には示されていない import static 宣言を介してメソッドが適切にインポートされたと想定します。

最初のコードリストは、フィールドインジェクションに @Autowired を使用するテストクラスの JUnit Jupiter ベースの実装を示しています。

Java
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    @Autowired
    HibernateTitleRepository titleRepository;

    @Test
    void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}
Kotlin
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    @Autowired
    lateinit var titleRepository: HibernateTitleRepository

    @Test
    fun findById() {
        val title = titleRepository.findById(10)
        assertNotNull(title)
    }
}

または、次のように、setter インジェクションに @Autowired を使用するようにクラスを構成できます。

Java
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    HibernateTitleRepository titleRepository;

    @Autowired
    void setTitleRepository(HibernateTitleRepository titleRepository) {
        this.titleRepository = titleRepository;
    }

    @Test
    void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}
Kotlin
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    lateinit var titleRepository: HibernateTitleRepository

    @Autowired
    fun setTitleRepository(titleRepository: HibernateTitleRepository) {
        this.titleRepository = titleRepository
    }

    @Test
    fun findById() {
        val title = titleRepository.findById(10)
        assertNotNull(title)
    }
}

上記のコードリストでは、@ContextConfiguration アノテーション(つまり repository-config.xml)によって参照される同じ XML コンテキストファイルを使用しています。以下にこの構成を示します。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
    <bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <!-- configuration elided for brevity -->
    </bean>

</beans>

setter メソッドの 1 つで @Autowired を使用する Spring 提供のテストベースクラスから拡張している場合、アプリケーションコンテキストで定義された影響を受けるタイプの複数の Bean(たとえば、複数の DataSource Bean)があります。そのような場合、次のように、setter メソッドをオーバーライドし、@Qualifier アノテーションを使用して特定のターゲット Bean を示すことができます(ただし、スーパークラスのオーバーライドされたメソッドにも委譲するようにしてください)。

Java
// ...

    @Autowired
    @Override
    public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) {
        super.setDataSource(dataSource);
    }

// ...
Kotlin
// ...

    @Autowired
    override fun setDataSource(@Qualifier("myDataSource") dataSource: DataSource) {
        super.setDataSource(dataSource)
    }

// ...

指定された修飾子の値は、注入する特定の DataSource Bean を示し、特定の Bean に一致するタイプのセットを絞り込みます。その値は、対応する <bean> 定義内の <qualifier> 宣言と照合されます。Bean 名はフォールバック修飾子の値として使用されるため、そこにある名前で特定の Bean を効果的に指すこともできます(前述のように、myDataSource が Bean id であると想定)。

3.5.7. リクエストおよびセッションスコープの Bean のテスト

Spring は初期からリクエストおよびセッションスコープの Bean をサポートしており、次の手順に従ってリクエストスコープおよびセッションスコープの Bean をテストできます。

  • テストクラスに @WebAppConfiguration のアノテーションを付けて、WebApplicationContext がテスト用にロードされていることを確認します。

  • モックリクエストまたはセッションをテストインスタンスに挿入し、必要に応じてテストフィクスチャを準備します。

  • 構成済みの WebApplicationContext から取得した Web コンポーネントを呼び出します(依存性注入を使用)。

  • モックに対してアサーションを実行します。

次のコードスニペットは、ログインユースケースの XML 構成を示しています。userService Bean は、リクエストスコープの loginAction Bean に依存していることに注意してください。また、LoginAction は、現在の HTTP リクエストからユーザー名とパスワードを取得する SpEL 式を使用してインスタンス化されます。このテストでは、TestContext フレームワークによって管理されるモックを介してこれらのリクエストパラメーターを構成します。次のリストは、このユースケースの構成を示しています。

リクエストスコープの Bean 構成
<beans>

    <bean id="userService" class="com.example.SimpleUserService"
            c:loginAction-ref="loginAction"/>

    <bean id="loginAction" class="com.example.LoginAction"
            c:username="#{request.getParameter('user')}"
            c:password="#{request.getParameter('pswd')}"
            scope="request">
        <aop:scoped-proxy/>
    </bean>

</beans>

RequestScopedBeanTests では、UserService (つまりテスト対象)と MockHttpServletRequest の両方をテストインスタンスに注入します。requestScope() テストメソッド内で、提供された MockHttpServletRequest でリクエストパラメーターを設定することにより、テストフィクスチャをセットアップします。userService で loginUser() メソッドが呼び出されると、ユーザーサービスが現在の MockHttpServletRequest (つまり、パラメーターを設定したもの)のリクエスト範囲 loginAction にアクセスできることが保証されます。その後、ユーザー名とパスワードの既知の入力に基づいて、結果に対してアサーションを実行できます。次のリストは、その方法を示しています。

Java
@SpringJUnitWebConfig
class RequestScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpServletRequest request;

    @Test
    void requestScope() {
        request.setParameter("user", "enigma");
        request.setParameter("pswd", "$pr!ng");

        LoginResults results = userService.loginUser();
        // assert results
    }
}
Kotlin
@SpringJUnitWebConfig
class RequestScopedBeanTests {

    @Autowired lateinit var userService: UserService
    @Autowired lateinit var request: MockHttpServletRequest

    @Test
    fun requestScope() {
        request.setParameter("user", "enigma")
        request.setParameter("pswd", "\$pr!ng")

        val results = userService.loginUser()
        // assert results
    }
}

次のコードスニペットは、リクエストスコープの Bean について先ほど見たものに似ています。ただし、今回は、userService Bean はセッションスコープの userPreferences Bean に依存しています。UserPreferences Bean は、現在の HTTP セッションからテーマを取得する SpEL 式を使用してインスタンス化されることに注意してください。このテストでは、TestContext フレームワークによって管理されるモックセッションでテーマを構成する必要があります。次の例は、その方法を示しています。

セッションスコープの Bean 構成
<beans>

    <bean id="userService" class="com.example.SimpleUserService"
            c:userPreferences-ref="userPreferences" />

    <bean id="userPreferences" class="com.example.UserPreferences"
            c:theme="#{session.getAttribute('theme')}"
            scope="session">
        <aop:scoped-proxy/>
    </bean>

</beans>

SessionScopedBeanTests では、UserService と MockHttpSession をテストインスタンスに注入します。sessionScope() テストメソッド内で、提供された MockHttpSession で予想される theme 属性を設定することにより、テストフィクスチャをセットアップします。userService で processUserPreferences() メソッドが呼び出されると、ユーザーサービスが現在の MockHttpSession のセッションスコープの userPreferences にアクセスできることが保証され、構成されたテーマに基づいて結果に対してアサーションを実行できます。次の例は、その方法を示しています。

Java
@SpringJUnitWebConfig
class SessionScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpSession session;

    @Test
    void sessionScope() throws Exception {
        session.setAttribute("theme", "blue");

        Results results = userService.processUserPreferences();
        // assert results
    }
}
Kotlin
@SpringJUnitWebConfig
class SessionScopedBeanTests {

    @Autowired lateinit var userService: UserService
    @Autowired lateinit var session: MockHttpSession

    @Test
    fun sessionScope() {
        session.setAttribute("theme", "blue")

        val results = userService.processUserPreferences()
        // assert results
    }
}

3.5.8. トランザクション管理

TestContext フレームワークでは、テストクラスで @TestExecutionListeners を明示的に宣言しなくても、トランザクションはデフォルトで設定される TransactionalTestExecutionListener によって管理されます。ただし、トランザクションのサポートを有効にするには、@ContextConfiguration セマンティクスでロードされる ApplicationContext で PlatformTransactionManager Bean を構成する必要があります(詳細については後で説明します)。さらに、テストのクラスレベルまたはメソッドレベルで Spring の @Transactional アノテーションを宣言する必要があります。

テスト管理されたトランザクション

テスト管理トランザクションは、TransactionalTestExecutionListener を使用して宣言的に管理されるトランザクション、または TestTransaction (後述)を使用してプログラムによって管理されるトランザクションです。このようなトランザクションを、Spring 管理のトランザクション(テスト用に読み込まれた ApplicationContext 内の Spring によって直接管理されるもの)やアプリケーション管理のトランザクション(テストによって呼び出されるアプリケーションコード内でプログラムによって管理されるもの)と混同しないでください。通常、Spring 管理およびアプリケーション管理のトランザクションは、テスト管理のトランザクションに参加します。ただし、Spring 管理またはアプリケーション管理のトランザクションが REQUIRED または SUPPORTS 以外の伝搬タイプで構成されている場合は注意が必要です(詳細については、トランザクションの伝搬に関する説明を参照してください)。

プリエンプティブタイムアウトとテスト管理されたトランザクション

Spring のテスト管理トランザクションと組み合わせて、テストフレームワークからプリエンプティブタイムアウトを使用する場合は注意が必要です。

特に、Spring のテストサポートは、現在のテストメソッドが呼び出される前に、トランザクション状態を(java.lang.ThreadLocal 変数を介して)現在のスレッドにバインドします。テストフレームワークがプリエンプティブタイムアウトをサポートするために新しいスレッドで現在のテストメソッドを呼び出した場合、現在のテストメソッド内で実行されたアクションはテスト管理トランザクション内で呼び出されません。そのようなアクションの結果は、テスト管理されたトランザクションではロールバックされません。それどころか、テスト管理のトランザクションが Spring によって適切にロールバックされたとしても、そのようなアクションは永続ストア(たとえば、リレーショナルデータベース)にコミットされます。

これが発生する可能性のある状況には、以下が含まれますが、これらに限定されません。

  • JUnit 4 の @Test(timeout = …​) サポートと TimeOut ルール

  • org.junit.jupiter.api.Assertions クラスの JUnit Jupiter の assertTimeoutPreemptively(…​) メソッド

  • TestNG の @Test(timeOut = …​) サポート

トランザクションの有効化と無効化

@Transactional を使用してテストメソッドにアノテーションを付けると、テストはトランザクション内で実行され、デフォルトでは、テストの補完後に自動的にロールバックされます。テストクラスに @Transactional アノテーションが付けられている場合、そのクラス階層内の各テストメソッドはトランザクション内で実行されます。(クラスまたはメソッドレベルで) @Transactional アノテーションが付けられていないテストメソッドは、トランザクション内で実行されません。@Transactional はテストライフサイクルメソッドではサポートされていないことに注意してください。たとえば、JUnit Jupiter の @BeforeAll@BeforeEach などのアノテーションが付けられたメソッドなどです。さらに、@Transactional がアノテーション付けされているが propagation 属性が NOT_SUPPORTED に設定されているテストはトランザクション内で実行されません

表 1: @Transactional 属性のサポート
属性 テスト管理されたトランザクションでサポート

value および transactionManager

はい

propagation

Propagation.NOT_SUPPORTED のみがサポートされます

isolation

いいえ

timeout

いいえ

readOnly

いいえ

rollbackFor および rollbackForClassName

いいえ : 代わりに TestTransaction.flagForRollback() を使用してください

noRollbackFor および noRollbackForClassName

いいえ : 代わりに TestTransaction.flagForCommit() を使用してください

メソッドレベルのライフサイクルメソッド — たとえば、JUnit Jupiter の @BeforeEach または @AfterEach でアノテーションが付けられたメソッド — テスト管理トランザクション内で実行されます。一方、スイートレベルおよびクラスレベルのライフサイクルメソッド — たとえば、JUnit Jupiter の @BeforeAll または @AfterAll でアノテーションが付けられたメソッドと TestNG の @BeforeSuite@AfterSuite@BeforeClass または @AfterClass でアノテーションが付けられたメソッド — テスト管理トランザクション内で実行されません

トランザクション内でスイートレベルまたはクラスレベルのライフサイクルメソッドでコードを実行する必要がある場合は、対応する PlatformTransactionManager をテストクラスに挿入し、それを TransactionTemplate と共に使用してプログラムによるトランザクション管理を行うことができます。

AbstractTransactionalJUnit4SpringContextTests および AbstractTransactionalTestNGSpringContextTests は、クラスレベルでのトランザクションサポート用に事前構成されていることに注意してください。

次の例は、Hibernate ベースの UserRepository の統合テストを記述する一般的なシナリオを示しています。

Java
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {

    @Autowired
    HibernateUserRepository repository;

    @Autowired
    SessionFactory sessionFactory;

    JdbcTemplate jdbcTemplate;

    @Autowired
    void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    void createUser() {
        // track initial state in test database:
        final int count = countRowsInTable("user");

        User user = new User(...);
        repository.save(user);

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush();
        assertNumUsers(count + 1);
    }

    private int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    private void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
@Transactional
class HibernateUserRepositoryTests {

    @Autowired
    lateinit var repository: HibernateUserRepository

    @Autowired
    lateinit var sessionFactory: SessionFactory

    lateinit var jdbcTemplate: JdbcTemplate

    @Autowired
    fun setDataSource(dataSource: DataSource) {
        this.jdbcTemplate = JdbcTemplate(dataSource)
    }

    @Test
    fun createUser() {
        // track initial state in test database:
        val count = countRowsInTable("user")

        val user = User()
        repository.save(user)

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush()
        assertNumUsers(count + 1)
    }

    private fun countRowsInTable(tableName: String): Int {
        return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
    }

    private fun assertNumUsers(expected: Int) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
    }
}

トランザクションのロールバックとコミットの動作で説明したように、データベースに加えられた変更は TransactionalTestExecutionListener によって自動的にロールバックされるため、createUser() メソッドの実行後にデータベースをクリーンアップする必要はありません。

トランザクションのロールバックとコミットの動作

デフォルトでは、テストトランザクションはテストの補完後に自動的にロールバックされます。ただし、@Commit および @Rollback アノテーションを使用して、トランザクションのコミットおよびロールバックの動作を宣言的に構成できます。詳細については、アノテーションサポートセクションの対応するエントリを参照してください。

プログラムによるトランザクション管理

TestTransaction の静的メソッドを使用して、プログラムでテスト管理されたトランザクションと対話できます。例:テストメソッド内、メソッドの前、およびメソッドの後に TestTransaction を使用して、現在のテスト管理トランザクションを開始または終了したり、ロールバックまたはコミットのために現在のテスト管理トランザクションを構成したりできます。TransactionalTestExecutionListener が有効になると、TestTransaction のサポートが自動的に使用可能になります。

次の例は、TestTransaction の機能の一部を示しています。詳細については、TestTransaction(Javadoc) の javadoc を参照してください。

Java
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
        AbstractTransactionalJUnit4SpringContextTests {

    @Test
    public void transactionalTest() {
        // assert initial state in test database:
        assertNumUsers(2);

        deleteFromTables("user");

        // changes to the database will be committed!
        TestTransaction.flagForCommit();
        TestTransaction.end();
        assertFalse(TestTransaction.isActive());
        assertNumUsers(0);

        TestTransaction.start();
        // perform other actions against the database that will
        // be automatically rolled back after the test completes...
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}
Kotlin
@ContextConfiguration(classes = [TestConfig::class])
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {

    @Test
    fun transactionalTest() {
        // assert initial state in test database:
        assertNumUsers(2)

        deleteFromTables("user")

        // changes to the database will be committed!
        TestTransaction.flagForCommit()
        TestTransaction.end()
        assertFalse(TestTransaction.isActive())
        assertNumUsers(0)

        TestTransaction.start()
        // perform other actions against the database that will
        // be automatically rolled back after the test completes...
    }

    protected fun assertNumUsers(expected: Int) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
    }
}
トランザクション外でのコードの実行

場合によっては、トランザクションテストメソッドの前または後に、トランザクションコンテキストの外で特定のコードを実行する必要があります。たとえば、テストを実行する前にデータベースの初期状態を確認したり、テストの実行後に予想されるトランザクションコミット動作を確認したりします(テストはトランザクションをコミットするように構成されました)。TransactionalTestExecutionListener は、まさにそのようなシナリオの @BeforeTransaction および @AfterTransaction アノテーションをサポートします。テストクラスの void メソッドまたはテストインターフェースの void デフォルトメソッドにこれらのアノテーションのいずれかでアノテーションを付けることができます。TransactionalTestExecutionListener は、前トランザクションメソッドまたは後トランザクションメソッドが適切なタイミングで実行されるようにします。

before メソッド(JUnit Jupiter の @BeforeEach アノテーションが付けられたメソッドなど)および after メソッド(JUnit Jupiter の @AfterEach アノテーションが付けられたメソッドなど)は、トランザクション内で実行されます。さらに、@BeforeTransaction または @AfterTransaction アノテーションが付けられたメソッドは、トランザクション内で実行するように構成されていないテストメソッドでは実行されません。
トランザクションマネージャーの構成

TransactionalTestExecutionListener は、テストのために PlatformTransactionManager Bean が Spring ApplicationContext で定義されることを期待しています。テストの ApplicationContext 内に PlatformTransactionManager のインスタンスが複数ある場合は、@Transactional("myTxMgr") または @Transactional(transactionManager = "myTxMgr") を使用して修飾子を宣言するか、@Configuration クラスによって TransactionManagementConfigurer を実装できます。テストの ApplicationContext でトランザクションマネージャーを検索するために使用されるアルゴリズムの詳細については、TestContextTransactionUtils.retrieveTransactionManager() の javadoc を参照してください。

すべてのトランザクション関連のアノテーションのデモンストレーション

次の JUnit Jupiter ベースの例は、すべてのトランザクション関連のアノテーションを強調する架空の統合テストシナリオを示しています。この例は、ベストプラクティスを示すことを目的としたものではなく、これらのアノテーションの使用方法を示すことを目的としています。詳細および構成例については、アノテーションサポートのセクションを参照してください。@Sql のトランザクション管理には、@Sql を使用して、デフォルトのトランザクションロールバックセマンティクスで宣言的な SQL スクリプトを実行する追加の例が含まれています。次の例は、関連するアノテーションを示しています。

Java
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {

    @BeforeTransaction
    void verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @BeforeEach
    void setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level @Commit setting
    @Rollback
    void modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @AfterEach
    void tearDownWithinTransaction() {
        // run "tear down" logic within the transaction
    }

    @AfterTransaction
    void verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}
Kotlin
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {

    @BeforeTransaction
    fun verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @BeforeEach
    fun setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level @Commit setting
    @Rollback
    fun modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @AfterEach
    fun tearDownWithinTransaction() {
        // run "tear down" logic within the transaction
    }

    @AfterTransaction
    fun verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}
ORM コードのテスト時に誤検知を回避する

Hibernate セッションまたは JPA 永続コンテキストの状態を操作するアプリケーションコードをテストするときは、そのコードを実行するテストメソッド内の基になる作業単位を必ずフラッシュしてください。基礎となる作業単位のフラッシュに失敗すると、誤検知が発生する可能性があります。テストは成功しますが、本番環境で同じコードが例外をスローします。これは、メモリ内の作業単位を維持する ORM フレームワークに適用されることに注意してください。次の Hibernate ベースのテストケースでは、1 つのメソッドが偽陽性を示し、もう 1 つのメソッドがセッションをフラッシュした結果を正しく公開しています。

Java
// ...

@Autowired
SessionFactory sessionFactory;

@Transactional
@Test // no expected exception!
public void falsePositive() {
    updateEntityInHibernateSession();
    // False positive: an exception will be thrown once the Hibernate
    // Session is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
public void updateWithSessionFlush() {
    updateEntityInHibernateSession();
    // Manual flush is required to avoid false positive in test
    sessionFactory.getCurrentSession().flush();
}

// ...
Kotlin
// ...

@Autowired
lateinit var sessionFactory: SessionFactory

@Transactional
@Test // no expected exception!
fun falsePositive() {
    updateEntityInHibernateSession()
    // False positive: an exception will be thrown once the Hibernate
    // Session is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
fun updateWithSessionFlush() {
    updateEntityInHibernateSession()
    // Manual flush is required to avoid false positive in test
    sessionFactory.getCurrentSession().flush()
}

// ...

次の例は、JPA の一致方法を示しています。

Java
// ...

@PersistenceContext
EntityManager entityManager;

@Transactional
@Test // no expected exception!
public void falsePositive() {
    updateEntityInJpaPersistenceContext();
    // False positive: an exception will be thrown once the JPA
    // EntityManager is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
public void updateWithEntityManagerFlush() {
    updateEntityInJpaPersistenceContext();
    // Manual flush is required to avoid false positive in test
    entityManager.flush();
}

// ...
Kotlin
// ...

@PersistenceContext
lateinit var entityManager:EntityManager

@Transactional
@Test // no expected exception!
fun falsePositive() {
    updateEntityInJpaPersistenceContext()
    // False positive: an exception will be thrown once the JPA
    // EntityManager is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
void updateWithEntityManagerFlush() {
    updateEntityInJpaPersistenceContext()
    // Manual flush is required to avoid false positive in test
    entityManager.flush()
}

// ...

3.5.9. SQL スクリプトの実行

リレーショナルデータベースに対して統合テストを作成する場合、SQL スクリプトを実行してデータベーススキーマを変更したり、テストデータをテーブルに挿入したりすることが多くの場合有益です。spring-jdbc モジュールは、Spring ApplicationContext がロードされたときに SQL スクリプトを実行することにより、組み込みまたは既存のデータベースを初期化するためのサポートを提供します。詳細については、組み込みデータベースのサポートおよび組み込みデータベースを使用したデータアクセスロジックのテストを参照してください。

それは ApplicationContext がロードされたときに一度テストするためのデータベースを初期化するために非常に有用であるが、時には統合テスト中にデータベースを変更することができることが不可欠です。次のセクションでは、統合テスト中に SQL スクリプトをプログラムおよび実行により実行する方法について説明します。

プログラムによる SQL スクリプトの実行

Spring には、統合テストメソッド内でプログラムによって SQL スクリプトを実行するための以下のオプションがあります。

  • org.springframework.jdbc.datasource.init.ScriptUtils

  • org.springframework.jdbc.datasource.init.ResourceDatabasePopulator

  • org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests

  • org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests

ScriptUtils は、SQL スクリプトを操作するための静的ユーティリティメソッドのコレクションを提供し、主にフレームワーク内での内部使用を目的としています。ただし、SQL スクリプトの解析方法と実行方法を完全に制御する必要がある場合、ScriptUtils は、後で説明する他の代替手段よりもニーズに適している場合があります。詳細については、ScriptUtils の個々のメソッドの javadoc を参照してください。

ResourceDatabasePopulator は、外部リソースで定義された SQL スクリプトを使用して、プログラムでデータベースにデータを入力、初期化、またはクリーンアップするためのオブジェクトベースの API を提供します。ResourceDatabasePopulator には、スクリプトの解析および実行時に使用される文字エンコード、ステートメント区切り文字、コメント区切り文字、およびエラー処理フラグを構成するためのオプションがあります。各構成オプションには、妥当なデフォルト値があります。デフォルト値の詳細については、javadoc を参照してください。ResourceDatabasePopulator で構成されたスクリプトを実行するには、populate(Connection) メソッドを呼び出して、java.sql.Connection に対してポピュレーターを実行するか、execute(DataSource) メソッドを呼び出して、javax.sql.DataSource に対してポピュレーターを実行します。次の例では、テストスキーマとテストデータの SQL スクリプトを指定し、ステートメントセパレーターを @@ に設定し、スクリプトを DataSource に対して実行します。

Java
@Test
void databaseTest() {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScripts(
            new ClassPathResource("test-schema.sql"),
            new ClassPathResource("test-data.sql"));
    populator.setSeparator("@@");
    populator.execute(this.dataSource);
    // run code that uses the test schema and data
}
Kotlin
@Test
fun databaseTest() {
    val populator = ResourceDatabasePopulator()
    populator.addScripts(
            ClassPathResource("test-schema.sql"),
            ClassPathResource("test-data.sql"))
    populator.setSeparator("@@")
    populator.execute(dataSource)
    // run code that uses the test schema and data
}

ResourceDatabasePopulator は、SQL スクリプトの解析と実行のために内部で ScriptUtils に委譲することに注意してください。同様に、AbstractTransactionalJUnit4SpringContextTests および AbstractTransactionalTestNGSpringContextTests の executeSqlScript(..) メソッドは、内部で ResourceDatabasePopulator を使用して SQL スクリプトを実行します。詳細については、さまざまな executeSqlScript(..) メソッドの Javadoc を参照してください。

@Sql を使用して SQL スクリプトを宣言的に実行する

プログラムで SQL スクリプトを実行するための前述のメカニズムに加えて、Spring TestContext フレームワークで SQL スクリプトを宣言的に構成できます。具体的には、テストクラスまたはテストメソッドで @Sql アノテーションを宣言して、統合テストメソッドの前または後に特定のデータベースに対して実行する SQL スクリプトへの個々の SQL ステートメントまたはリソースパスを構成できます。@Sql のサポートは SqlScriptsTestExecutionListener によって提供され、デフォルトで有効になっています。

メソッドレベルの @Sql 宣言は、デフォルトでクラスレベルの宣言をオーバーライドします。ただし、Spring Framework 5.2, 以降、この動作は @SqlMergeMode を介してテストクラスごとまたはテストメソッドごとに構成できます。詳細については、@SqlMergeMode を使用した構成のマージとオーバーライドを参照してください。
パスリソースセマンティクス

各パスは Spring Resource として解釈されます。プレーンパス("schema.sql" など)は、テストクラスが定義されているパッケージに関連するクラスパスリソースとして扱われます。スラッシュで始まるパスは、絶対クラスパスリソースとして扱われます(たとえば、"/org/example/schema.sql")。URL を参照するパス(たとえば、接頭辞 classpath:file:http: のパス)は、指定されたリソースプロトコルを使用してロードされます。

次の例は、JUnit Jupiter ベースの統合テストクラス内で、クラスレベルおよびメソッドレベルで @Sql を使用する方法を示しています。

Java
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {

    @Test
    void emptySchemaTest() {
        // run code that uses the test schema without any test data
    }

    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    void userTest() {
        // run code that uses the test schema and test data
    }
}
Kotlin
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {

    @Test
    fun emptySchemaTest() {
        // run code that uses the test schema without any test data
    }

    @Test
    @Sql("/test-schema.sql", "/test-user-data.sql")
    fun userTest() {
        // run code that uses the test schema and test data
    }
}
デフォルトのスクリプト検出

SQL スクリプトまたはステートメントが指定されていない場合、@Sql が宣言されている場所に応じて、default スクリプトの検出が試行されます。デフォルトを検出できない場合、IllegalStateException がスローされます。

  • クラスレベルの宣言 : アノテーション付きテストクラスが com.example.MyTest の場合、対応するデフォルトスクリプトは classpath:com/example/MyTest.sql です。

  • メソッドレベルの宣言 : アノテーション付きテストメソッドの名前が testMethod() で、クラス com.example.MyTest で定義されている場合、対応するデフォルトスクリプトは classpath:com/example/MyTest.testMethod.sql です。

複数の @Sql セットの宣言

特定のテストクラスまたはテストメソッドに対して複数の SQL スクリプトセットを構成する必要があるが、構文構成、エラー処理ルール、またはセットごとに異なる実行フェーズが異なる場合、@Sql の複数のインスタンスを宣言できます。Java 8 では、@Sql を繰り返し可能なアノテーションとして使用できます。それ以外の場合は、@SqlGroup アノテーションを、@Sql の複数のインスタンスを宣言するための明示的なコンテナーとして使用できます。

次の例は、@Sql を Java 8 で繰り返し可能なアノテーションとして使用する方法を示しています。

Java
@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
    // run code that uses the test schema and test data
}
Kotlin
// Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin

前の例で示したシナリオでは、test-schema.sql スクリプトは単一行コメントに異なる構文を使用します。

次の例は、@Sql 宣言が @SqlGroup 内でグループ化されていることを除いて、前述の例と同じです。Java 8 以上では、@SqlGroup の使用はオプションですが、Kotlin などの他の JVM 言語との互換性のために @SqlGroup を使用する必要がある場合があります。

Java
@Test
@SqlGroup({
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
void userTest() {
    // run code that uses the test schema and test data
}
Kotlin
@Test
@SqlGroup(
    Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
    Sql("/test-user-data.sql"))
fun userTest() {
    // Run code that uses the test schema and test data
}
スクリプト実行フェーズ

デフォルトでは、SQL スクリプトは対応するテストメソッドの前に実行されます。ただし、テストメソッドの後に特定のスクリプトセットを実行する必要がある場合(データベースの状態をクリーンアップする場合など)は、次の例に示すように、@Sql の executionPhase 属性を使用できます。

Java
@Test
@Sql(
    scripts = "create-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
    scripts = "delete-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED),
    executionPhase = AFTER_TEST_METHOD
)
void userTest() {
    // run code that needs the test data to be committed
    // to the database outside of the test's transaction
}
Kotlin
@Test
@SqlGroup(
    Sql("create-test-data.sql",
        config = SqlConfig(transactionMode = ISOLATED)),
    Sql("delete-test-data.sql",
        config = SqlConfig(transactionMode = ISOLATED),
        executionPhase = AFTER_TEST_METHOD))
fun userTest() {
    // run code that needs the test data to be committed
    // to the database outside of the test's transaction
}

ISOLATED および AFTER_TEST_METHOD は、それぞれ Sql.TransactionMode および Sql.ExecutionPhase から静的にインポートされることに注意してください。

@SqlConfig を使用したスクリプト構成

@SqlConfig アノテーションを使用して、スクリプトの解析とエラー処理を構成できます。@SqlConfig は、統合テストクラスでクラスレベルのアノテーションとして宣言されると、テストクラス階層内のすべての SQL スクリプトのグローバル構成として機能します。@Sql アノテーションの config 属性を使用して直接宣言された場合、@SqlConfig は、囲む @Sql アノテーション内で宣言された SQL スクリプトのローカル構成として機能します。@SqlConfig のすべての属性には暗黙のデフォルト値があり、対応する属性の javadoc にドキュメント化されています。Java 言語仕様のアノテーション属性に定義されている規則のため、残念ながら、null の値をアノテーション属性に割り当てることはできません。継承されたグローバル構成のオーバーライドをサポートするために、@SqlConfig 属性には、"" (ストリングの場合)、{} (配列の場合)、または DEFAULT (列挙の場合)の明示的なデフォルト値があります。このアプローチにより、@SqlConfig のローカル宣言は、""{} または DEFAULT 以外の値を提供することにより、@SqlConfig のグローバル宣言から個々の属性を選択的にオーバーライドできます。グローバル @SqlConfig 属性は、ローカル @SqlConfig 属性が ""{} または DEFAULT 以外の明示的な値を提供しない場合に常に継承されます。明示的なローカル設定はグローバル設定を上書きします。

@Sql および @SqlConfig によって提供される構成オプションは、ScriptUtils および ResourceDatabasePopulator によってサポートされるものと同等ですが、<jdbc:initialize-database/> XML 名前空間要素によって提供される構成オプションのスーパーセットです。詳細については、@Sql(Javadoc) および @SqlConfig(Javadoc) の個々の属性の javadoc を参照してください。

@Sql のトランザクション管理

デフォルトでは、SqlScriptsTestExecutionListener は @Sql を使用して構成されたスクリプトに必要なトランザクションセマンティクスを推測します。具体的には、SQL スクリプトは、transactionMode 属性の設定値に応じて、トランザクションなしで、既存の Spring 管理トランザクション(たとえば、@Transactional アノテーションが付けられたテスト用の TransactionalTestExecutionListener によって管理されるトランザクション)、または分離されたトランザクション内で実行されます。@SqlConfig およびテストの ApplicationContext の PlatformTransactionManager の存在。ただし、最低限、テストの ApplicationContext に javax.sql.DataSource が存在する必要があります。

SqlScriptsTestExecutionListener が DataSource および PlatformTransactionManager を検出してトランザクションセマンティクスを推測するために使用するアルゴリズムがニーズに合わない場合は、@SqlConfig の dataSource および transactionManager 属性を設定して明示的な名前を指定できます。さらに、@SqlConfig (たとえば、スクリプトを分離されたトランザクションで実行するかどうか) の transactionMode 属性を設定することにより、トランザクション伝播の動作を制御できます。@Sql でサポートされているすべてのトランザクション管理オプションについての詳細な説明は、このリファレンスマニュアルの範囲外ですが、@SqlConfig(Javadoc) SqlScriptsTestExecutionListener(Javadoc) の javadoc には詳細な情報が記載されています。次の例は、JUnit Jupiter と @Sql によるトランザクションテストを使用した一般的なテストシナリオを示しています。

Java
@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {

    final JdbcTemplate jdbcTemplate;

    @Autowired
    TransactionalSqlScriptsTests(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    @Sql("/test-data.sql")
    void usersTest() {
        // verify state in test database:
        assertNumUsers(2);
        // run code that uses the test data...
    }

    int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    void assertNumUsers(int expected) {
        assertEquals(expected, countRowsInTable("user"),
            "Number of rows in the [user] table.");
    }
}
Kotlin
@SpringJUnitConfig(TestDatabaseConfig::class)
@Transactional
class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) {

    val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource)

    @Test
    @Sql("/test-data.sql")
    fun usersTest() {
        // verify state in test database:
        assertNumUsers(2)
        // run code that uses the test data...
    }

    fun countRowsInTable(tableName: String): Int {
        return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
    }

    fun assertNumUsers(expected: Int) {
        assertEquals(expected, countRowsInTable("user"),
                "Number of rows in the [user] table.")
    }
}

(テストメソッド内または /test-data.sql スクリプト内で)データベースに加えられた変更は TransactionalTestExecutionListener によって自動的にロールバックされるため、usersTest() メソッドの実行後にデータベースをクリーンアップする必要がないことに注意してください(詳細については、トランザクション管理を参照してください))。

@SqlMergeMode を使用した構成のマージとオーバーライド

Spring Framework 5.2, 以降、メソッドレベルの @Sql 宣言をクラスレベルの宣言とマージすることが可能です。例:これにより、データベーススキーマの構成またはいくつかの一般的なテストデータをテストクラスごとに 1 回提供し、テストメソッドごとに追加のユースケース固有のテストデータを提供できます。@Sql マージを有効にするには、テストクラスまたはテストメソッドに @SqlMergeMode(MERGE) でアノテーションを付けます。特定のテストメソッド(または特定のテストサブクラス)のマージを無効にするには、@SqlMergeMode(OVERRIDE) を使用してデフォルトモードに戻すことができます。例と詳細については、@SqlMergeMode アノテーションドキュメントセクションを参照してください。

3.5.10. 並列テスト実行

Spring Framework 5.0 は、Spring TestContext フレームワークを使用する場合に、単一の JVM 内で並行してテストを実行するための基本的なサポートを導入しました。一般に、これは、ほとんどのテストクラスまたはテストメソッドを、テストコードや構成を変更せずに並行して実行できることを意味します。

並列テスト実行の設定方法の詳細については、テストフレームワーク、ビルドツール、または IDE のドキュメントを参照してください。

テストスイートに同時実行性を導入すると、予期しない副作用、奇妙なランタイム動作、テストが断続的またはランダムに失敗する可能性があることに注意してください。Spring チームは、テストを並行して実行しない場合の次の一般的なガイドラインを提供します。

次の場合は、テストを並行して実行しないでください。

  • Spring Framework の @DirtiesContext サポートを使用します。

  • Spring Boot の @MockBean または @SpyBean サポートを使用します。

  • JUnit 4 の @FixMethodOrder サポート、またはテストメソッドが特定の順序で実行されるように設計されたテストフレームワーク機能を使用します。ただし、テストクラス全体が並行して実行される場合、これは当てはまりません。

  • データベース、メッセージブローカー、ファイルシステムなどの共有サービスまたはシステムの状態を変更します。これは、組み込みシステムと外部システムの両方に適用されます。

現在のテストの ApplicationContext がアクティブでなくなったことを示す例外で並列テストの実行が失敗した場合、これは通常、異なるスレッドで ApplicationContext が ContextCache から削除されたことを意味します。

これは、@DirtiesContext の使用または ContextCache からの自動追い出しが原因である可能性があります。@DirtiesContext が原因である場合は、@DirtiesContext の使用を回避する方法を見つけるか、そのようなテストを並列実行から除外する必要があります。ContextCache の最大サイズを超えた場合、キャッシュの最大サイズを増やすことができます。詳細については、コンテキストキャッシングに関する説明を参照してください。

Spring TestContext フレームワークでの並列テスト実行は、TestContext(Javadoc) の javadoc に従って、基礎となる TestContext 実装がコピーコンストラクターを提供する場合にのみ可能です。Spring で使用される DefaultTestContext は、このようなコンストラクターを提供します。ただし、カスタム TestContext 実装を提供するサードパーティライブラリを使用する場合、それが並列テストの実行に適していることを確認する必要があります。

3.5.11. TestContext フレームワークサポートクラス

このセクションでは、Spring TestContext フレームワークをサポートするさまざまなクラスについて説明します。

Spring JUnit 4 ランナー

Spring TestContext フレームワークは、カスタムランナー(JUnit 4.12 以上でサポート)を介して JUnit 4 と完全に統合されています。テストクラスに @RunWith(SpringJUnit4ClassRunner.class) または短い @RunWith(SpringRunner.class) バリアントをアノテーションすることにより、開発者は標準の JUnit 4 ベースのユニットおよび統合テストを実装し、同時にアプリケーションコンテキストのロードのサポート、テストインスタンスの依存性注入、トランザクションテストなど、TestContext フレームワークの利点を享受できます。メソッドの実行など。Spring TestContext フレームワークを代替ランナー(JUnit 4 の Parameterized ランナーなど)またはサードパーティランナー(MockitoJUnitRunner など)で使用する場合は、オプションで Spring の JUnit ルールのサポートを代わりに使用できます。

次のコードリストは、テストクラスをカスタム Spring Runner で実行するように構成するための最小要件を示しています。

Java
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {

    @Test
    public void testMethod() {
        // test logic...
    }
}
Kotlin
@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {

    @Test
    fun testMethod() {
        // test logic...
    }
}

上記の例では、@TestExecutionListeners が空のリストで構成され、デフォルトのリスナーを無効にします。そうしないと、ApplicationContext を @ContextConfiguration で構成する必要があります。

Spring JUnit 4 ルール

org.springframework.test.context.junit4.rules パッケージは、次の JUnit 4 ルールを提供します(JUnit 4.12 以降でサポートされます)。

  • SpringClassRule

  • SpringMethodRule

SpringClassRule は Spring TestContext フレームワークのクラスレベルの機能をサポートする JUnit TestRule であり、SpringMethodRule は Spring TestContext フレームワークのインスタンスレベルおよびメソッドレベルの機能をサポートする JUnit MethodRule です。

SpringRunner とは対照的に、Spring のルールベースの JUnit サポートには、org.junit.runner.Runner の実装に依存しないという利点があるため、既存の代替ランナー(JUnit 4 の Parameterized など)またはサードパーティランナー(MockitoJUnitRunner など)と組み合わせることができます)。

TestContext フレームワークのすべての機能をサポートするには、SpringClassRule と SpringMethodRule を組み合わせる必要があります。次の例は、統合テストでこれらのルールを宣言する適切な方法を示しています。

Java
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {

    @ClassRule
    public static final SpringClassRule springClassRule = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Test
    public void testMethod() {
        // test logic...
    }
}
Kotlin
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
class IntegrationTest {

    @Rule
    val springMethodRule = SpringMethodRule()

    @Test
    fun testMethod() {
        // test logic...
    }

    companion object {
        @ClassRule
        val springClassRule = SpringClassRule()
    }
}
JUnit 4 サポートクラス

org.springframework.test.context.junit4 パッケージは、JUnit 4 ベースのテストケースに次のサポートクラスを提供します(JUnit 4.12 以上でサポートされます)。

  • AbstractJUnit4SpringContextTests

  • AbstractTransactionalJUnit4SpringContextTests

AbstractJUnit4SpringContextTests は、Spring TestContext フレームワークと JUnit 4 環境での明示的な ApplicationContext テストサポートを統合する抽象ベーステストクラスです。AbstractJUnit4SpringContextTests を継承すると、protectedapplicationContext インスタンス変数にアクセスして、明示的な Bean ルックアップを実行したり、コンテキスト全体の状態をテストしたりできます。

AbstractTransactionalJUnit4SpringContextTests は、AbstractJUnit4SpringContextTests の抽象トランザクション拡張であり、JDBC アクセスに便利な機能を追加します。このクラスは、javax.sql.DataSource Bean と PlatformTransactionManager Bean が ApplicationContext で定義されることを期待しています。AbstractTransactionalJUnit4SpringContextTests を継承すると、データベースを照会する SQL ステートメントを実行するために使用できる protectedjdbcTemplate インスタンス変数にアクセスできます。このようなクエリを使用して、データベース関連のアプリケーションコードを実行する前後にデータベースの状態を確認できます。Spring は、そのようなクエリがアプリケーションコードと同じトランザクションのスコープで実行されるようにします。ORM ツールと組み合わせて使用する場合は、必ず検知を避けてください。JDBC テストのサポートで記述されていたように、AbstractTransactionalJUnit4SpringContextTests は、前述の jdbcTemplate を使用して JdbcTestUtils のメソッドに委譲する便利なメソッドも提供します。さらに、AbstractTransactionalJUnit4SpringContextTests は、構成された DataSource に対して SQL スクリプトを実行するための executeSqlScript(..) メソッドを提供します。

これらのクラスは拡張に便利です。テストクラスを Spring 固有のクラス階層に結び付けたくない場合は、@RunWith(SpringRunner.class) または Spring の JUnit ルールを使用して独自のカスタムテストクラスを構成できます。
JUnit Jupiter 用 SpringExtension

Spring TestContext フレームワークは、JUnit 5 で導入された JUnit Jupiter テストフレームワークとの完全な統合を提供します。@ExtendWith(SpringExtension.class) でテストクラスにアノテーションを付けることで、標準の JUnit Jupiter ベースのユニットテストと統合テストを実装し、同時に TestContext フレームワークのメリットを享受できます。アプリケーションコンテキストのロード、テストインスタンスの依存性注入、トランザクションテストメソッドの実行などのサポート。

さらに、JUnit Jupiter の豊富な拡張 API のおかげで、Spring は、Spring が JUnit 4 および TestNG に対してサポートする機能セットに加えて、以下の機能を提供します。

次のコードリストは、SpringExtension を @ContextConfiguration と組み合わせて使用するようにテストクラスを構成する方法を示しています。

Java
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {

    @Test
    void testMethod() {
        // test logic...
    }
}
Kotlin
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension::class)
// Instructs Spring to load an ApplicationContext from TestConfig::class
@ContextConfiguration(classes = [TestConfig::class])
class SimpleTests {

    @Test
    fun testMethod() {
        // test logic...
    }
}

JUnit 5 のアノテーションもメタアノテーションとして使用できるため、Spring は @SpringJUnitConfig および @SpringJUnitWebConfig 構成アノテーションを提供して、テスト ApplicationContext および JUnit Jupiter の構成を簡素化します。

次の例では、@SpringJUnitConfig を使用して、前の例で使用した構成の量を削減します。

Java
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {

    @Test
    void testMethod() {
        // test logic...
    }
}
Kotlin
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig::class)
class SimpleTests {

    @Test
    fun testMethod() {
        // test logic...
    }
}

同様に、次の例では、@SpringJUnitWebConfig を使用して、JUnit Jupiter で使用する WebApplicationContext を作成します。

Java
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {

    @Test
    void testMethod() {
        // test logic...
    }
}
Kotlin
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig::class
@SpringJUnitWebConfig(TestWebConfig::class)
class SimpleWebTests {

    @Test
    fun testMethod() {
        // test logic...
    }
}

詳細については、Spring JUnit Jupiter テストアノテーションの @SpringJUnitConfig および @SpringJUnitWebConfig の資料を参照してください。

SpringExtension による依存性注入

SpringExtension は、JUnit Jupiter の ParameterResolver (英語) 拡張 API を実装します。これにより、Spring は、テストコンストラクター、テストメソッド、およびテストライフサイクルコールバックメソッドに依存性注入を提供できます。

具体的には、SpringExtension は、テストの ApplicationContext から @BeforeAll@AfterAll@BeforeEach@AfterEach@Test@RepeatedTest@ParameterizedTest などのアノテーションが付けられたテストコンストラクターおよびメソッドに依存関係を注入できます。

コンストラクターインジェクション

JUnit Jupiter テストクラスのコンストラクターの特定のパラメーターがタイプ ApplicationContext (またはそのサブタイプ)であるか、または @Autowired@Qualifier、または @Value でアノテーションまたはメタアノテーションが付けられている場合、Spring はその特定のパラメーターの値に対応する Bean または値を挿入します。テストの ApplicationContext から。

Spring は、コンストラクターが autowirable であると見なされる場合、テストクラスコンストラクターのすべての引数をオートワイヤーするように構成することもできます。コンストラクターは、次の条件のいずれかが(優先順位の順に)満たされる場合、自動書き込み可能と見なされます。

  • コンストラクターには @Autowired のアノテーションが付けられます。

  • @TestConstructor は、autowireMode 属性が ALL に設定されたテストクラスに存在またはメタ存在します。

  • デフォルトのテストコンストラクターのオートワイヤーモードは ALL に変更されました。

@TestConstructor の使用と、グローバルテストコンストラクターのオートワイヤーモードを変更する方法の詳細については、@TestConstructor を参照してください

テストクラスのコンストラクターが autowirable であると見なされる場合、Spring はコンストラクター内のすべてのパラメーターの引数を解決する責任を負います。その結果、JUnit Jupiter に登録されている他の ParameterResolver は、そのようなコンストラクターのパラメーターを解決できません。

@DirtiesContext を使用してテストメソッドの前または後にテストの ApplicationContext を閉じる場合、テストクラスのコンストラクター注入を JUnit Jupiter の @TestInstance(PER_CLASS) サポートと組み合わせて使用しないでください。

その理由は、@TestInstance(PER_CLASS) が JUnit Jupiter にテストメソッドの呼び出しの間にテストインスタンスをキャッシュするように指示するためです。その結果、テストインスタンスは、その後閉じられた ApplicationContext から最初に注入された Bean への参照を保持します。このようなシナリオではテストクラスのコンストラクターが 1 回だけ呼び出されるため、依存関係の注入は再度行われず、後続のテストは閉じられた ApplicationContext の Bean と対話し、エラーが発生する可能性があります。

@TestInstance(PER_CLASS) と組み合わせて @DirtiesContext を「テストメソッド前」または「テストメソッド後」モードで使用するには、Spring からの依存関係をフィールドまたは setter インジェクションを介して提供し、テストメソッド呼び出しの間に再インジェクトできるように構成する必要があります。

次の例では、Spring は、TestConfig.class から OrderServiceIntegrationTests コンストラクターにロードされた ApplicationContext から OrderService Bean を注入します。

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

    @Autowired
    OrderServiceIntegrationTests(OrderService orderService) {
        this.orderService = orderService;
    }

    // tests that use the injected OrderService
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){
    // tests that use the injected OrderService
}

この機能により、テストの依存関係が final になり、不変になります。

spring.test.constructor.autowire.mode プロパティが all の場合(@TestConstructor を参照)、前の例のコンストラクターでの @Autowired の宣言を省略して、次のようにすることができます。

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

    OrderServiceIntegrationTests(OrderService orderService) {
        this.orderService = orderService;
    }

    // tests that use the injected OrderService
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests(val orderService:OrderService) {
    // tests that use the injected OrderService
}
メソッドインジェクション

JUnit Jupiter テストメソッドまたはテストライフサイクルコールバックメソッドのパラメーターが ApplicationContext タイプ(またはそのサブタイプ)であるか、または @Autowired@Qualifier、または @Value でアノテーションまたはメタアノテーションが付けられている場合、Spring は対応する Bean でその特定のパラメーターの値を注入します。テストの ApplicationContext

次の例では、Spring は TestConfig.class から deleteOrder() テストメソッドにロードされた ApplicationContext から OrderService を挿入します。

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    @Test
    void deleteOrder(@Autowired OrderService orderService) {
        // use orderService from the test's ApplicationContext
    }
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests {

    @Test
    fun deleteOrder(@Autowired orderService: OrderService) {
        // use orderService from the test's ApplicationContext
    }
}

JUnit Jupiter の ParameterResolver サポートの堅牢性により、Spring だけでなく、JUnit Jupiter 自体または他のサードパーティの拡張機能からも、単一のメソッドに複数の依存関係を注入できます。

次の例は、Spring と JUnit Jupiter の両方で placeOrderRepeatedly() テストメソッドに依存関係を同時に注入する方法を示しています。

Java
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    @RepeatedTest(10)
    void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
            @Autowired OrderService orderService) {

        // use orderService from the test's ApplicationContext
        // and repetitionInfo from JUnit Jupiter
    }
}
Kotlin
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests {

    @RepeatedTest(10)
    fun placeOrderRepeatedly(repetitionInfo:RepetitionInfo, @Autowired orderService:OrderService) {

        // use orderService from the test's ApplicationContext
        // and repetitionInfo from JUnit Jupiter
    }
}

JUnit Jupiter の @RepeatedTest を使用すると、テストメソッドが RepetitionInfo にアクセスできることに注意してください。

TestNG サポートクラス

org.springframework.test.context.testng パッケージは、TestNG ベースのテストケースに対して以下のサポートクラスを提供します。

  • AbstractTestNGSpringContextTests

  • AbstractTransactionalTestNGSpringContextTests

AbstractTestNGSpringContextTests は、Spring TestContext フレームワークを TestNG 環境での明示的な ApplicationContext テストサポートと統合する抽象基本テストクラスです。AbstractTestNGSpringContextTests を継承すると、protectedapplicationContext インスタンス変数にアクセスして、明示的な Bean ルックアップを実行したり、コンテキスト全体の状態をテストしたりできます。

AbstractTransactionalTestNGSpringContextTests は、AbstractTestNGSpringContextTests の抽象トランザクション拡張であり、JDBC アクセスに便利な機能を追加します。このクラスは、javax.sql.DataSource Bean と PlatformTransactionManager Bean が ApplicationContext で定義されることを期待しています。AbstractTransactionalTestNGSpringContextTests を継承すると、データベースを照会する SQL ステートメントを実行するために使用できる protectedjdbcTemplate インスタンス変数にアクセスできます。このようなクエリを使用して、データベース関連のアプリケーションコードを実行する前後にデータベースの状態を確認できます。Spring は、そのようなクエリがアプリケーションコードと同じトランザクションのスコープで実行されるようにします。ORM ツールと組み合わせて使用する場合は、必ず検知を避けてください。JDBC テストのサポートで記述されていたように、AbstractTransactionalTestNGSpringContextTests は、前述の jdbcTemplate を使用して JdbcTestUtils のメソッドに委譲する便利なメソッドも提供します。さらに、AbstractTransactionalTestNGSpringContextTests は、構成された DataSource に対して SQL スクリプトを実行するための executeSqlScript(..) メソッドを提供します。

これらのクラスは拡張に便利です。テストクラスを Spring 固有のクラス階層に結び付けたくない場合は、@ContextConfiguration@TestExecutionListeners などを使用するか、TestContextManager を使用してテストクラスを手動でインストルメントすることにより、独自のカスタムテストクラスを構成できます。テストクラスをインスツルメントする方法の例については、AbstractTestNGSpringContextTests のソースコードを参照してください。

3.6. Spring MVC テストフレームワーク

Spring MVC テストフレームワークは、JUnit、TestNG、またはその他のテストフレームワークで使用できる流れるような API で Spring MVC コードをテストするためのファーストクラスのサポートを提供します。spring-test モジュールのサーブレット API モックオブジェクト上に構築されているため、実行中のサーブレットコンテナーは使用しません。DispatcherServlet を使用して、完全な Spring MVC ランタイム動作を提供し、スタンドアロンモードに加えて、TestContext フレームワークで実際の Spring 構成をロードするサポートを提供します。スタンドアロンモードでは、コントローラーを一度に 1 つずつインスタンス化してテストできます。

Spring MVC Test は、RestTemplate を使用するコードをテストするためのクライアント側サポートも提供します。クライアント側のテストはサーバーのレスポンスをモックし、実行中のサーバーも使用しません。

Spring Boot は、実行中のサーバーを含む完全なエンドツーエンド統合テストを作成するオプションを提供します。これがゴールの場合は、Spring Boot リファレンスガイドを参照してください。コンテナー外とエンドツーエンドの統合テストの違いの詳細については、Spring MVC テストとエンドツーエンドテストを参照してください。

3.6.1. サーバー側のテスト

JUnit または TestNG を使用して、Spring MVC コントローラーの単純な単体テストを作成できます。そのためには、コントローラーをインスタンス化し、モックまたはスタブされた依存関係を注入し、そのメソッドを呼び出します(必要に応じて MockHttpServletRequestMockHttpServletResponse などを渡します)。ただし、このような単体テストを作成する場合、リクエストマッピング、データバインディング、型変換、検証など、多くのテストが行われていません。さらに、@InitBinder@ModelAttribute や @ExceptionHandler などの他のコントローラーメソッドも、リクエスト処理ライフサイクルの一部として呼び出すことができます。

Spring MVC Test のゴールは、リクエストを実行し、実際の DispatcherServlet を介してレスポンスを生成することにより、コントローラーをテストする効果的な方法を提供することです。

Spring MVC テストは、spring-test モジュールで利用可能なサーブレット API の使い慣れた「モック」実装に基づいています。これにより、サーブレットコンテナーで実行する必要なく、リクエストを実行してレスポンスを生成できます。Spring MVC テストとエンドツーエンドテストに従って、ほとんどの場合、いくつかの注目すべき例外を除き、すべてが実行時と同じように機能します。次の JUnit Jupiter ベースの例では、Spring MVC テストを使用しています。

Java
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.;

@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class ExampleTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    void getAccount() throws Exception {
        this.mockMvc.perform(get("/accounts/1")
                .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json"))
            .andExpect(jsonPath("$.name").value("Lee"));
    }
}
Kotlin
import org.springframework.test.web.servlet.get

@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class ExampleTests {

    lateinit var mockMvc: MockMvc

    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }

    @Test
    fun getAccount() {
        mockMvc.get("/accounts/1") {
            accept = MediaType.APPLICATION_JSON
        }.andExpect {
            status { isOk }
            content { contentType(MediaType.APPLICATION_JSON) }
            jsonPath("$.name") { value("Lee") }
        }
    }
}
Kotlin では専用の MockMvc DSL が利用可能です

上記のテストは、TestContext フレームワークの WebApplicationContext サポートに依存して、テストクラスと同じパッケージにある XML 構成ファイルから Spring 構成をロードしますが、Java ベースおよび Groovy ベースの構成もサポートされています。これらのサンプルテスト (GitHub) を参照してください。

MockMvc インスタンスは、/accounts/1 への GET リクエストを実行し、結果のレスポンスのステータスが 200 であり、コンテンツタイプが application/json であり、レスポンスボディの値が Lee の name という JSON プロパティを持つことを確認するために使用されます。jsonPath 構文は、Jayway JsonPath プロジェクト (GitHub) を通じてサポートされています。実行されたリクエストの結果を検証するためのその他の多くのオプションについては、このドキュメントで後述します。

静的インポート

前のセクションの例の流暢な API では、MockMvcRequestBuilders.*MockMvcResultMatchers.* や MockMvcBuilders.* などのいくつかの静的インポートが必要です。これらのクラスを見つける簡単な方法は、MockMvc* に一致するタイプを検索することです。Eclipse または Eclipse Pleiades All in One (STS, Lombok 付属) または Eclipse 用 Spring Tools (英語) を使用する場合は、Java →エディター→コンテンツアシスト→お気に入りの Eclipse 設定で「お気に入りの静的メンバー」として追加してください。これにより、静的メソッド名の最初の文字を入力した後にコンテンツアシストを使用できます。他の IDE(IntelliJ など)では、追加の構成は必要ありません。静的メンバーのコード補完のサポートを確認してください。

セットアップの選択

MockMvc のインスタンスを作成するには、主に 2 つのオプションがあります。1 つ目は、TestContext フレームワークを介して Spring MVC 構成をロードすることです。TestContext フレームワークは、Spring 構成をロードし、WebApplicationContext をテストに挿入して、MockMvc インスタンスの作成に使用します。次の例は、そのメソッドを示しています。

Java
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    // ...

}
Kotlin
@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {

    lateinit var mockMvc: MockMvc

    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }

    // ...

}

2 番目のオプションは、Spring 構成をロードせずにコントローラーインスタンスを手動で作成することです。代わりに、MVC JavaConfig または MVC 名前空間の構成にほぼ匹敵する基本的なデフォルト構成が自動的に作成されます。ある程度カスタマイズできます。次の例は、その方法を示しています。

Java
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
    }

    // ...

}
Kotlin
class MyWebTests {

    lateinit var mockMvc : MockMvc

    @BeforeEach
    fun setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
    }

    // ...

}

どのセットアップオプションを使用する必要がありますか?

webAppContextSetup は、実際の Spring MVC 構成をロードし、より完全な統合テストを実施します。TestContext フレームワークはロードされた Spring 構成をキャッシュするため、テストスイートにさらに多くのテストを導入する場合でも、テストを高速に実行し続けるのに役立ちます。さらに、Spring 構成を通じてモックサービスをコントローラーに注入して、Web レイヤーのテストに集中することができます。次の例では、Mockito でモックサービスを宣言しています。

<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="org.example.AccountService"/>
</bean>

次に、次の例に示すように、モックサービスをテストに挿入して、期待を設定および検証できます。

Java
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class AccountTests {

    @Autowired
    AccountService accountService;

    MockMvc mockMvc;

    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    // ...

}
Kotlin
@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class AccountTests {

    @Autowired
    lateinit var accountService: AccountService

    lateinit mockMvc: MockMvc

    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }

    // ...

}

一方、standaloneSetup は、ユニットテストに少し近づいています。一度に 1 つのコントローラーをテストします。コントローラーにモック依存関係を手動で挿入できます。Spring 構成のロードは必要ありません。このようなテストはスタイルに重点を置いており、どのコントローラーがテストされているか、特定の Spring MVC 構成が動作するために必要かどうかなどを簡単に確認できます。standaloneSetup は、特定の動作を検証したり課題をデバッグしたりするためのアドホックテストを作成する非常に便利な方法でもあります。

ほとんどの「統合と単体テスト」の議論と同様に、正解も不正解もありません。ただし、standaloneSetup を使用すると、Spring MVC 構成を検証するために、webAppContextSetup テストを追加する必要があります。または、常に実際の Spring MVC 構成に対してテストするために、すべてのテストを webAppContextSetup で作成できます。

セットアップ機能

使用する MockMvc ビルダーに関係なく、すべての MockMvcBuilder 実装は、いくつかの一般的で非常に便利な機能を提供します。例:次のように、すべてのリクエストに対して Accept ヘッダーを宣言し、すべてのレスポンスに Content-Type ヘッダーと同様に 200 のステータスを期待できます。

Java
// static import of MockMvcBuilders.standaloneSetup

MockMvc mockMvc = standaloneSetup(new MusicController())
    .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

さらに、サードパーティのフレームワーク(およびアプリケーション)は、MockMvcConfigurer にあるようなセットアップ手順を事前にパッケージ化できます。Spring Framework には、リクエスト間で HTTP セッションを保存および再利用するのに役立つ組み込み実装が 1 つあります。次のように使用できます。

Java
// static import of SharedHttpSessionConfigurer.sharedHttpSession

MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
        .apply(sharedHttpSession())
        .build();

// Use mockMvc to perform requests...
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

すべての MockMvc ビルダー機能のリストについては、ConfigurableMockMvcBuilder(Javadoc) の javadoc を参照するか、IDE を使用して使用可能なオプションを調べましょう。

リクエストの実行

次の例に示すように、任意の HTTP メソッドを使用するリクエストを実行できます。

Java
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
Kotlin
import org.springframework.test.web.servlet.post

mockMvc.post("/hotels/{id}", 42) {
    accept = MediaType.APPLICATION_JSON
}

内部で MockMultipartHttpServletRequest を使用するファイルアップロードリクエストを実行して、マルチパートリクエストの実際の解析が行われないようにすることもできます。むしろ、次の例のように設定する必要があります。

Java
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
Kotlin
import org.springframework.test.web.servlet.multipart

mockMvc.multipart("/doc") {
    file("a1", "ABC".toByteArray(charset("UTF8")))
}

次の例に示すように、クエリテンプレートを URI テンプレートスタイルで指定できます。

Java
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));
Kotlin
mockMvc.get("/hotels?thing={thing}", "somewhere")

次の例に示すように、クエリまたはフォームパラメーターを表すサーブレットリクエストパラメーターを追加することもできます。

Java
mockMvc.perform(get("/hotels").param("thing", "somewhere"));
Kotlin
import org.springframework.test.web.servlet.get

mockMvc.get("/hotels") {
    param("thing", "somewhere")
}

アプリケーションコードがサーブレットリクエストパラメーターに依存しており、クエリ文字列を明示的にチェックしない場合(ほとんどの場合)、使用するオプションは関係ありません。ただし、URI テンプレートで提供されるクエリパラメーターはデコードされますが、param(…​) メソッドを介して提供されるリクエストパラメーターはすでにデコードされていることが予想されます。

ほとんどの場合、コンテキスト URI とサーブレットパスはリクエスト URI から除外することをお勧めします。完全なリクエスト URI でテストする必要がある場合は、次の例に示すように、リクエストマッピングが機能するように、contextPath と servletPath を必ず設定してください。

Java
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
Kotlin
import org.springframework.test.web.servlet.get

mockMvc.get("/app/main/hotels/{id}") {
    contextPath = "/app"
    servletPath = "/main"
}

上記の例では、実行されたすべてのリクエストで contextPath および servletPath を設定するのは面倒です。代わりに、次の例に示すように、デフォルトのリクエストプロパティを設定できます。

Java
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        mockMvc = standaloneSetup(new AccountController())
            .defaultRequest(get("/")
            .contextPath("/app").servletPath("/main")
            .accept(MediaType.APPLICATION_JSON)).build();
    }
}
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

上記のプロパティは、MockMvc インスタンスを介して実行されるすべてのリクエストに影響します。特定のリクエストで同じプロパティが指定されている場合、デフォルト値が上書きされます。そのため、デフォルトのリクエストの HTTP メソッドと URI は重要ではありません。リクエストごとに指定する必要があるためです。

期待の定義

次の例に示すように、リクエストの実行後に 1 つ以上の .andExpect(..) 呼び出しを追加することにより、期待を定義できます。

Java
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
Kotlin
import org.springframework.test.web.servlet.get

mockMvc.get("/accounts/1").andExpect {
    status().isOk()
}

MockMvcResultMatchers.* は多くの期待を提供しますが、その中にはさらに入れ子になってさらに詳細な期待があります。

期待は 2 つの一般的なカテゴリに分類されます。アサーションの最初のカテゴリは、レスポンスのプロパティ(レスポンスステータス、ヘッダー、コンテンツなど)を検証します。これらは、主張する最も重要な結果です。

アサーションの 2 番目のカテゴリは、レスポンスを超えています。これらのアサーションにより、どのコントローラーメソッドがリクエストを処理したか、例外が発生して処理されたかどうか、モデルの内容、選択されたビュー、追加されたフラッシュ属性など、Spring MVC 固有の側面をインスペクションできます。また、リクエストやセッション属性など、サーブレット固有の側面をインスペクションできます。

次のテストは、バインディングまたは検証が失敗したことを表明します。

Java
mockMvc.perform(post("/persons"))
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
Kotlin
import org.springframework.test.web.servlet.post

mockMvc.post("/persons").andExpect {
    status().isOk()
    model {
        attributeHasErrors("person")
    }
}

多くの場合、テストを作成するとき、実行されたリクエストの結果をダンプすることが有用です。print() は MockMvcResultHandlers からの静的インポートです。

Java
mockMvc.perform(post("/persons"))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
Kotlin
import org.springframework.test.web.servlet.post

mockMvc.post("/persons").andDo {
        print()
    }.andExpect {
        status().isOk()
        model {
            attributeHasErrors("person")
        }
    }

リクエスト処理によって未処理の例外が発生しない限り、print() メソッドは、利用可能なすべての結果データを System.out に出力します。log() メソッドと print() メソッドの 2 つの追加バリアントもあります。1 つは OutputStream を受け入れ、もう 1 つは Writer を受け入れます。例: print(System.err) を呼び出すと結果データが System.err に出力され、print(myWriter) を呼び出すと結果データがカスタムライターに出力されます。結果データを出力する代わりにログに記録する場合は、log() メソッドを呼び出して、結果データを org.springframework.test.web.servlet.result ロギングカテゴリに単一の DEBUG メッセージとして記録します。

場合によっては、結果に直接アクセスして、他の方法では検証できないものを検証したい場合があります。これは、次の例に示すように、他のすべての期待値の後に .andReturn() を追加することで実現できます。

Java
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
Kotlin
var mvcResult = mockMvc.post("/persons").andExpect { status().isOk() }.andReturn()
// ...

すべてのテストが同じ期待値を繰り返す場合、次の例に示すように、MockMvc インスタンスを構築するときに共通の期待値を 1 回設定できます。

Java
standaloneSetup(new SimpleController())
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build()
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

共通の期待は常に適用され、別の MockMvc インスタンスを作成せずに上書きすることはできません。

JSON レスポンスコンテンツに Spring HATEOAS (GitHub) で作成されたハイパーメディアリンクが含まれる場合、次の例に示すように、JsonPath 式を使用して、結果のリンクを確認できます。

Java
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
Kotlin
mockMvc.get("/people") {
    accept(MediaType.APPLICATION_JSON)
}.andExpect {
    jsonPath("$.links[?(@.rel == 'self')].href") {
        value("http://localhost:8080/people")
    }
}

XML レスポンスコンテンツに Spring HATEOAS (GitHub) で作成されたハイパーメディアリンクが含まれている場合、XPath 式を使用して、結果のリンクを確認できます。

Java
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
    .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
Kotlin
val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
    accept(MediaType.APPLICATION_XML)
}.andExpect {
    xpath("/person/ns:link[@rel='self']/@href", ns) {
        string("http://localhost:8080/people")
    }
}
非同期リクエスト

Spring MVC でサポートされている Servlet 3.0 非同期リクエストは、サーブレットコンテナースレッドを終了し、アプリケーションが非同期にレスポンスを計算できるようにすることで機能します。その後、非同期ディスパッチが行われ、サーブレットコンテナースレッドの処理が完了します。

Spring MVC テストでは、最初に生成された非同期値をアサートし、次に手動で非同期ディスパッチを実行し、最後にレスポンスを検証することにより、非同期リクエストをテストできます。以下は、DeferredResultCallable、または Reactor Mono などのリアクティブ型を返すコントローラーメソッドのテスト例です。

Java
@Test
void test() throws Exception {
    MvcResult mvcResult = this.mockMvc.perform(get("/path"))
            .andExpect(status().isOk()) (1)
            .andExpect(request().asyncStarted()) (2)
            .andExpect(request().asyncResult("body")) (3)
            .andReturn();

    this.mockMvc.perform(asyncDispatch(mvcResult)) (4)
            .andExpect(status().isOk()) (5)
            .andExpect(content().string("body"));
}
1 レスポンスステータスを変更しないでください
2 非同期処理が開始されている必要があります
3 待機して非同期結果をアサートする
4ASYNC ディスパッチを手動で実行する (実行中のコンテナーがないため)
5 最終レスポンスを確認する
Kotlin
@Test
fun test() {
    var mvcResult = mockMvc.get("/path").andExpect {
        status().isOk() (1)
        request { asyncStarted() } (2)
        // TODO Remove unused generic parameter
        request { asyncResult<Nothing>("body") } (3)
    }.andReturn()


    mockMvc.perform(asyncDispatch(mvcResult)) (4)
            .andExpect {
                status().isOk() (5)
                content().string("body")
            }
}
1 レスポンスステータスを変更しないでください
2 非同期処理が開始されている必要があります
3 待機して非同期結果をアサートする
4ASYNC ディスパッチを手動で実行する (実行中のコンテナーがないため)
5 最終レスポンスを確認する
ストリーミングレスポンス

Spring MVC Test には、ストリーミングレスポンスのコンテナーレステスト用のオプションが組み込まれていません。Spring MVC ストリーミングオプションを使用するアプリケーションは、WebTestClient を使用して、実行中のサーバーに対してエンドツーエンドの統合テストを実行できます。これは、WebTestClient実行中のサーバーをテストできる Spring Boot でもサポートされています。もう 1 つの利点は、プロジェクト Reactor の StepVerifier を使用して、データストリームに対する期待を宣言できることです。

フィルター登録

MockMvc インスタンスをセットアップするとき、次の例に示すように、1 つ以上のサーブレット Filter インスタンスを登録できます。

Java
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

登録されたフィルターは、spring-test から MockFilterChain を介して呼び出され、最後のフィルターは DispatcherServlet に委譲されます。

Spring MVC テストとエンドツーエンドテスト

Spring MVC テストは、spring-test モジュールのサーブレット API モック実装に基づいて構築されており、実行中のコンテナーに依存しません。実際のクライアントと稼働中のサーバーを実行する完全なエンドツーエンド統合テストと比較すると、いくつかの違いがあります。

これについて考える最も簡単な方法は、空の MockHttpServletRequest から始めることです。それに追加するものは何でもリクエストはなります。驚くかもしれないのは、デフォルトではコンテキストパスがないことです。jsessionid Cookie なし ; 転送、エラー、または非同期ディスパッチなし。実際の JSP レンダリングはありません。代わりに、「転送された」および「リダイレクトされた」URL は MockHttpServletResponse に保存され、期待してアサートできます。

つまり、JSP を使用する場合、リクエストの転送先の JSP ページを検証できますが、HTML はレンダリングされません。つまり、JSP は呼び出されません。ただし、Thymeleaf や Freemarker など、転送に依存しない他のすべてのレンダリングテクノロジーでは、HTML が期待どおりにレスポンス本文にレンダリングされることに注意してください。@ResponseBody メソッドを介して JSON、XML、およびその他の形式をレンダリングする場合も同様です。

または、Spring Boot と @SpringBootTest の完全なエンドツーエンド統合テストのサポートを検討することもできます。Spring Boot リファレンスガイドを参照してください。

それぞれのアプローチには長所と短所があります。Spring MVC テストで提供されるオプションは、従来の単体テストから完全な統合テストまで、スケールの異なるストップです。確かに、Spring MVC テストのオプションはいずれも従来の単体テストのカテゴリに該当しませんが、少し近いものです。例:モックされたサービスをコントローラーに注入することにより、Web レイヤーを分離できます。この場合、実際の Spring 構成を使用して DispatcherServlet のみを介して Web レイヤーをテストします。また、スタンドアロンセットアップを使用して、一度に 1 つのコントローラーに焦点を合わせ、それを機能させるために必要な構成を手動で提供することもできます。

Spring MVC テストを使用する際のもう 1 つの重要な違いは、概念的には、このようなテストはサーバー側であるため、使用されたハンドラー、HandlerExceptionResolver で例外が処理された場合、モデルのコンテンツ、バインディングエラーを確認できることです。あり、その他の詳細。つまり、サーバーは実際の HTTP クライアントを介してテストするときのように、不透明なボックスではないため、期待を書くのが簡単です。これは通常、クラシックユニットテストの利点です。作成、推論、デバッグは簡単ですが、完全な統合テストの必要性を置き換えるものではありません。同時に、レスポンスがチェックする最も重要なものであるという事実を見失わないようにすることが重要です。つまり、同じプロジェクト内でも、複数のスタイルとテスト戦略を実行する余地があります。

さらなる例

フレームワーク自体のテストには、Spring MVC テストの使用方法を示すことを目的とした多くのサンプル (GitHub) テストが含まれています。これらの例を参照して、さらにアイデアを得ることができます。また、spring-mvc-showcase (GitHub) プロジェクトには、Spring MVC テストに基づいた完全なテストカバレッジがあります。

3.6.2. HtmlUnit 統合

Spring は、MockMvcHtmlUnit (英語) の統合を提供します。これにより、HTML ベースのビューを使用するときに、エンドツーエンドのテストを簡単に実行できます。この統合により、次のことが可能になります。

  • HtmlUnit (英語) WebDriver (英語) 、およびゲブ (英語) などのツールを使用して、サーブレットコンテナーにデプロイする必要なく、HTML ページを簡単にテストします。

  • ページ内で JavaScript をテストします。

  • 必要に応じて、モックサービスを使用してテストし、テストを高速化します。

  • コンテナー内のエンドツーエンドテストとコンテナー外の統合テスト間でロジックを共有します。

MockMvc は、サーブレットコンテナー(たとえば、Thymeleaf、FreeMarker など)に依存しないテンプレートテクノロジで動作しますが、サーブレットコンテナーに依存するため、JSP では動作しません。
HtmlUnit 統合の理由

頭に浮かぶ最も明白な質問は、「なぜこれが必要なのですか?」です。答えは、非常に基本的なサンプルアプリケーションを調べることで見つけることができます。Message オブジェクトで CRUD 操作をサポートする Spring MVC Web アプリケーションがあるとします。アプリケーションは、すべてのメッセージのページングもサポートしています。どのようにテストしますか?

Spring MVC テストを使用すると、次のように Message を作成できるかどうかを簡単にテストできます。

Java
MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param("summary", "Spring Rocks")
        .param("text", "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));
Kotlin
@Test
fun test() {
    mockMvc.post("/messages/") {
        param("summary", "Spring Rocks")
        param("text", "In case you didn't know, Spring Rocks!")
    }.andExpect {
        status().is3xxRedirection()
        redirectedUrl("/messages/123")
    }
}

メッセージを作成できるフォームビューをテストする場合はどうでしょうか。例:フォームが次のスニペットのように見えると仮定します。

<form id="messageForm" action="/messages/" method="post">
    <div class="pull-right"><a href="/messages/">Messages</a></div>

    <label for="summary">Summary</label>
    <input type="text" class="required" id="summary" name="summary" value="" />

    <label for="text">Message</label>
    <textarea id="text" name="text"></textarea>

    <div class="form-actions">
        <input type="submit" value="Create" />
    </div>
</form>

フォームが新しいメッセージを作成するための正しいリクエストを生成することをどのように確認しますか?素朴な試みは次のようになります。

Java
mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='summary']").exists())
        .andExpect(xpath("//textarea[@name='text']").exists());
Kotlin
mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='summary']") { exists() }
    xpath("//textarea[@name='text']") { exists() }
}

このテストにはいくつかの明らかな欠点があります。text の代わりにパラメーター message を使用するようにコントローラーを更新すると、HTML フォームがコントローラーと同期していない場合でも、フォームテストに合格し続けます。これを解決するために、次のように 2 つのテストを組み合わせることができます。

Java
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
        .andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());

MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param(summaryParamName, "Spring Rocks")
        .param(textParamName, "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));
Kotlin
val summaryParamName = "summary";
val textParamName = "text";
mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='$summaryParamName']") { exists() }
    xpath("//textarea[@name='$textParamName']") { exists() }
}
mockMvc.post("/messages/") {
    param(summaryParamName, "Spring Rocks")
    param(textParamName, "In case you didn't know, Spring Rocks!")
}.andExpect {
    status().is3xxRedirection()
    redirectedUrl("/messages/123")
}

これにより、テストが不合格になるリスクが軽減されますが、まだいくつかの問題があります。

  • ページに複数のフォームがある場合はどうなるでしょうか?確かに、XPath 式を更新できますが、より多くの要因を考慮すると、式はより複雑になります。フィールドは正しい型ですか?フィールドは有効になっていますか?等々。

  • もう 1 つの課題は、予想される作業の 2 倍を行っていることです。最初にビューを検証し、次に検証したばかりの同じパラメーターでビューを送信する必要があります。理想的には、これは一度にすべて実行できます。

  • 最後に、まだいくつかのことを説明できません。例:フォームに JavaScript 検証があり、それもテストしたい場合はどうなるでしょうか?

全体的な問題は、Web ページのテストに単一の対話が含まれないことです。代わりに、ユーザーが Web ページと対話する方法と、その Web ページが他のリソースと対話する方法の組み合わせです。例:フォームビューの結果は、メッセージを作成するためのユーザーへの入力として使用されます。さらに、フォームビューでは、JavaScript 検証など、ページの動作に影響を与える追加のリソースを潜在的に使用できます。

Rescue への統合テスト?

前述の課題を解決するには、エンドツーエンドの統合テストを実行できますが、これにはいくつかの欠点があります。メッセージをページングできるビューのテストを検討してください。次のテストが必要になる場合があります。

  • メッセージが空の場合、結果が利用できないことを示す通知がページに表示されますか?

  • ページに単一のメッセージが適切に表示されていますか?

  • ページはページングを適切にサポートしていますか?

これらのテストを設定するには、データベースに適切なメッセージが含まれていることを確認する必要があります。これは、いくつかの追加の課題につながります。

  • 適切なメッセージがデータベースにあることを確認するのは面倒です。(外部キー制約を考慮してください。)

  • 各テストではデータベースが正しい状態であることを確認する必要があるため、テストが遅くなる可能性があります。

  • データベースは特定の状態にする必要があるため、テストを並行して実行することはできません。

  • 自動生成された ID、タイムスタンプなどのアイテムでアサーションを実行することは困難です。

これらの課題は、エンドツーエンドの統合テストを完全に放棄する必要があるという意味ではありません。代わりに、詳細テストをリファクタリングして、より速く、より信頼性が高く、副作用のないモックサービスを使用することで、エンドツーエンドの統合テストの数を減らすことができます。その後、単純なワークフローを検証して、すべてが適切に連携することを確認する少数の真のエンドツーエンド統合テストを実装できます。

HtmlUnit 統合を開始

それでは、どのようにしてページの相互作用をテストし、それでもテストスイート内で良好なパフォーマンスを維持することのバランスをとることができますか?答えは、「MockMvc と HtmlUnit を統合することです。」

HtmlUnit 統合オプション

MockMvc と HtmlUnit を統合する場合、いくつかのオプションがあります。

  • MockMvc および HtmlUnit : 生の HtmlUnit ライブラリを使用する場合は、このオプションを使用します。

  • MockMvc および WebDriver : このオプションを使用して、統合テストとエンドツーエンドテストの間でコードの開発と再利用を容易にします。

  • MockMvc および Geb : Groovy をテストに使用し、開発を容易にし、統合テストとエンドツーエンドテストの間でコードを再利用する場合は、このオプションを使用します。

MockMvc および HtmlUnit

このセクションでは、MockMvc と HtmlUnit を統合する方法について説明します。生の HtmlUnit ライブラリを使用する場合は、このオプションを使用します。

MockMvc および HtmlUnit のセットアップ

最初に、net.sourceforge.htmlunit:htmlunit のテスト依存関係が含まれていることを確認してください。Apache HttpComponents 4.5+ で HtmlUnit を使用するには、HtmlUnit 2.18 以上を使用する必要があります。

次のように、MockMvcWebClientBuilder を使用して、MockMvc と統合する HtmlUnit WebClient を簡単に作成できます。

Java
WebClient webClient;

@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var webClient: WebClient

@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}
これは、MockMvcWebClientBuilder を使用する簡単な例です。高度な使用箇所については、高度な MockMvcWebClientBuilder を参照してください。

これにより、サーバーとして localhost を参照する URL が、実際の HTTP 接続を必要とせずに MockMvc インスタンスにリダイレクトされるようになります。他の URL は、通常どおりネットワーク接続を使用してリクエストされます。これにより、CDN の使用を簡単にテストできます。

MockMvc および HtmlUnit の使用箇所

これで、通常どおり HtmlUnit を使用できますが、アプリケーションをサーブレットコンテナーにデプロイする必要はありません。例:次のメッセージを作成するためにビューをリクエストできます:

Java
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
Kotlin
val createMsgFormPage = webClient.getPage("http://localhost/messages/form")
デフォルトのコンテキストパスは "" です。あるいは、高度な MockMvcWebClientBuilder に従って、コンテキストパスを指定できます。

HtmlPage への参照を取得したら、次の例に示すように、フォームに入力して送信し、メッセージを作成できます。

Java
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
Kotlin
val form = createMsgFormPage.getHtmlElementById("messageForm")
val summaryInput = createMsgFormPage.getHtmlElementById("summary")
summaryInput.setValueAttribute("Spring Rocks")
val textInput = createMsgFormPage.getHtmlElementById("text")
textInput.setText("In case you didn't know, Spring Rocks!")
val submit = form.getOneHtmlElementByAttribute("input", "type", "submit")
val newMessagePage = submit.click()

最後に、新しいメッセージが正常に作成されたことを確認できます。次のアサーションでは、AssertJ (英語) ライブラリを使用しています。

Java
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
Kotlin
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123")
val id = newMessagePage.getHtmlElementById("id").getTextContent()
assertThat(id).isEqualTo("123")
val summary = newMessagePage.getHtmlElementById("summary").getTextContent()
assertThat(summary).isEqualTo("Spring Rocks")
val text = newMessagePage.getHtmlElementById("text").getTextContent()
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!")

前述のコードは、さまざまな方法で MockMvc テストを改善しています。まず、フォームを明示的に検証してから、フォームのようなリクエストを作成する必要がなくなりました。代わりに、フォームをリクエストし、記入し、送信することにより、オーバーヘッドを大幅に削減します。

もう 1 つの重要な要素は、HtmlUnit は Mozilla Rhino エンジンを使用する (英語) が JavaScript を評価することです。つまり、ページ内で JavaScript の動作をテストすることもできます。

HtmlUnit の使用に関する追加情報については、HtmlUnit のドキュメント (英語) を参照してください。

高度な MockMvcWebClientBuilder

これまでの例では、Spring TestContext フレームワークによってロードされた WebApplicationContext に基づいて WebClient を構築することにより、可能な限り最も簡単な方法で MockMvcWebClientBuilder を使用しました。このアプローチは、次の例で繰り返されます。

Java
WebClient webClient;

@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var webClient: WebClient

@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}

次の例に示すように、追加の構成オプションを指定することもできます。

Java
WebClient webClient;

@BeforeEach
void setup() {
    webClient = MockMvcWebClientBuilder
        // demonstrates applying a MockMvcConfigurer (Spring Security)
        .webAppContextSetup(context, springSecurity())
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
}
Kotlin
lateinit var webClient: WebClient

@BeforeEach
fun setup() {
    webClient = MockMvcWebClientBuilder
        // demonstrates applying a MockMvcConfigurer (Spring Security)
        .webAppContextSetup(context, springSecurity())
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build()
}

別の方法として、次のように MockMvc インスタンスを個別に構成して MockMvcWebClientBuilder に提供することにより、まったく同じセットアップを実行できます。

Java
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

webClient = MockMvcWebClientBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

これはより冗長ですが、MockMvc インスタンスを使用して WebClient を構築することにより、指先で MockMvc の全機能を使用できます。

MockMvc インスタンスの作成に関する追加情報については、セットアップの選択を参照してください。
MockMvc および WebDriver

前のセクションでは、MockMvc を生の HtmlUnit API と組み合わせて使用する方法を見てきました。このセクションでは、Selenium WebDriver (英語) 内で追加の抽象化を使用して、物事をさらに簡単にします。

なぜ WebDriver と MockMvc なのでしょうか?

すでに HtmlUnit と MockMvc を使用できますが、なぜ WebDriver を使用するのでしょうか? Selenium WebDriver は非常にエレガントな API を提供しており、コードを簡単に整理できます。それがどのように機能するかをよりよく示すために、このセクションの例を調べます。

Selenium (英語) の一部であるにもかかわらず、WebDriver はテストを実行するために Selenium Server を必要としません。

メッセージが適切に作成されるようにする必要があるとします。テストには、HTML フォームの入力要素の検索、入力、さまざまなアサーションの作成が含まれます。

エラー状態もテストする必要があるため、このアプローチでは多数の個別のテストが行われます。例:フォームの一部のみを入力した場合にエラーが発生するようにします。フォーム全体に入力すると、新しく作成されたメッセージが後で表示されます。

フィールドの 1 つに「summary」という名前が付けられている場合、テスト内の複数の場所で次のようなものが繰り返される可能性があります。

Java
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
Kotlin
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)

id を smmry に変更するとどうなるでしょうか?これを行うと、すべてのテストを更新してこの変更を組み込むように強制されます。これは DRY の原則に違反するため、次のように、理想的にはこのコードを独自のメソッドに抽出する必要があります。

Java
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
    setSummary(currentPage, summary);
    // ...
}

public void setSummary(HtmlPage currentPage, String summary) {
    HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
    summaryInput.setValueAttribute(summary);
}
Kotlin
fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{
    setSummary(currentPage, summary);
    // ...
}

fun setSummary(currentPage:HtmlPage , summary: String) {
    val summaryInput = currentPage.getHtmlElementById("summary")
    summaryInput.setValueAttribute(summary)
}

そうすることで、UI を変更した場合にすべてのテストを更新する必要がなくなります。

次の例に示すように、これをさらに一歩進め、現在の HtmlPage を表す Object 内にこのロジックを配置することもできます。

Java
public class CreateMessagePage {

    final HtmlPage currentPage;

    final HtmlTextInput summaryInput;

    final HtmlSubmitInput submit;

    public CreateMessagePage(HtmlPage currentPage) {
        this.currentPage = currentPage;
        this.summaryInput = currentPage.getHtmlElementById("summary");
        this.submit = currentPage.getHtmlElementById("submit");
    }

    public <T> T createMessage(String summary, String text) throws Exception {
        setSummary(summary);

        HtmlPage result = submit.click();
        boolean error = CreateMessagePage.at(result);

        return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
    }

    public void setSummary(String summary) throws Exception {
        summaryInput.setValueAttribute(summary);
    }

    public static boolean at(HtmlPage page) {
        return "Create Message".equals(page.getTitleText());
    }
}
Kotlin
    class CreateMessagePage(private val currentPage: HtmlPage) {

        val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary")

        val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit")

        fun <T> createMessage(summary: String, text: String): T {
            setSummary(summary)

            val result = submit.click()
            val error = at(result)

            return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T
        }

        fun setSummary(summary: String) {
            summaryInput.setValueAttribute(summary)
        }

        fun at(page: HtmlPage): Boolean {
            return "Create Message" == page.getTitleText()
        }
    }
}

以前は、このパターンはページオブジェクトパターン (GitHub) として知られていました。確かに HtmlUnit でこれを行うことができますが、WebDriver には、このパターンを実装しやすくするために次のセクションで検討するいくつかのツールが用意されています。

MockMvc および WebDriver のセットアップ

Selenium WebDriver を Spring MVC テストフレームワークで使用するには、プロジェクトに org.seleniumhq.selenium:selenium-htmlunit-driver へのテスト依存関係が含まれていることを確認してください。

次の例に示すように、MockMvcHtmlUnitDriverBuilder を使用して、MockMvc と統合する Selenium WebDriver を簡単に作成できます。

Java
WebDriver driver;

@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var driver: WebDriver

@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}
これは、MockMvcHtmlUnitDriverBuilder を使用する簡単な例です。より高度な使用箇所については、高度な MockMvcHtmlUnitDriverBuilder を参照してください。

上記の例では、サーバーとして localhost を参照する URL が、実際の HTTP 接続を必要とせずに MockMvc インスタンスにリダイレクトされるようにします。他の URL は、通常どおりネットワーク接続を使用してリクエストされます。これにより、CDN の使用を簡単にテストできます。

MockMvc および WebDriver の使用箇所

これで、通常どおり WebDriver を使用できますが、アプリケーションをサーブレットコンテナーにデプロイする必要はありません。例:次のメッセージを作成するためにビューをリクエストできます:

Java
CreateMessagePage page = CreateMessagePage.to(driver);
Kotlin
val page = CreateMessagePage.to(driver)

次に、フォームに入力して送信し、次のようにメッセージを作成します。

Java
ViewMessagePage viewMessagePage =
        page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
Kotlin
val viewMessagePage =
    page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)

これにより、ページオブジェクトパターンを活用することにより、HtmlUnit テストの設計が改善されます。なぜ WebDriver と MockMvc なのでしょうか? で説明したように、HtmlUnit でページオブジェクトパターンを使用できますが、WebDriver でははるかに簡単です。次の CreateMessagePage 実装を検討してください。

Java
public class CreateMessagePage
        extends AbstractPage { (1)

    (2)
    private WebElement summary;
    private WebElement text;

    (3)
    @FindBy(css = "input[type=submit]")
    private WebElement submit;

    public CreateMessagePage(WebDriver driver) {
        super(driver);
    }

    public <T> T createMessage(Class<T> resultPage, String summary, String details) {
        this.summary.sendKeys(summary);
        this.text.sendKeys(details);
        this.submit.click();
        return PageFactory.initElements(driver, resultPage);
    }

    public static CreateMessagePage to(WebDriver driver) {
        driver.get("http://localhost:9990/mail/messages/form");
        return PageFactory.initElements(driver, CreateMessagePage.class);
    }
}
1CreateMessagePage は AbstractPage を継承します。AbstractPage の詳細については説明しませんが、要約すると、すべてのページに共通の機能が含まれています。例:アプリケーションにナビゲーションバー、グローバルエラーメッセージ、およびその他の機能がある場合、このロジックを共有の場所に配置できます。
2 関心のある HTML ページの各部分にメンバー変数があります。これらは WebElement タイプです。WebDriver の PageFactory (GitHub) では、各 WebElement を自動的に解決することにより、HtmlUnit バージョンの CreateMessagePage から多くのコードを削除できます。PageFactory#initElements(WebDriver,Class<T>) (英語) メソッドは、フィールド名を使用し、HTML ページ内の要素の id または name で検索することにより、各 WebElement を自動的に解決します。
3@FindBy アノテーション (GitHub) を使用して、デフォルトのルックアップ動作をオーバーライドできます。この例では、@FindBy アノテーションを使用して、css セレクター(input [type = submit])で送信ボタンを検索する方法を示します。
Kotlin
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { (1)

    (2)
    private lateinit var summary: WebElement
    private lateinit var text: WebElement

    (3)
    @FindBy(css = "input[type=submit]")
    private lateinit var submit: WebElement

    fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
        this.summary.sendKeys(summary)
        text.sendKeys(details)
        submit.click()
        return PageFactory.initElements(driver, resultPage)
    }
    companion object {
        fun to(driver: WebDriver): CreateMessagePage {
            driver.get("http://localhost:9990/mail/messages/form")
            return PageFactory.initElements(driver, CreateMessagePage::class.java)
        }
    }
}
1CreateMessagePage は AbstractPage を継承します。AbstractPage の詳細については説明しませんが、要約すると、すべてのページに共通の機能が含まれています。例:アプリケーションにナビゲーションバー、グローバルエラーメッセージ、およびその他の機能がある場合、このロジックを共有の場所に配置できます。
2 関心のある HTML ページの各部分にメンバー変数があります。これらは WebElement タイプです。WebDriver の PageFactory (GitHub) では、各 WebElement を自動的に解決することにより、HtmlUnit バージョンの CreateMessagePage から多くのコードを削除できます。PageFactory#initElements(WebDriver,Class<T>) (英語) メソッドは、フィールド名を使用し、HTML ページ内の要素の id または name で検索することにより、各 WebElement を自動的に解決します。
3@FindBy アノテーション (GitHub) を使用して、デフォルトのルックアップ動作をオーバーライドできます。この例では、@FindBy アノテーションを使用して、css セレクター(input [type = submit])で送信ボタンを検索する方法を示します。

最後に、新しいメッセージが正常に作成されたことを確認できます。以下のアサーションは、AssertJ (英語) アサーションライブラリを使用します。

Java
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
Kotlin
assertThat(viewMessagePage.message.isEqualTo(expectedMessage)
assertThat(viewMessagePage.success.isEqualTo("Successfully created a new message")

ViewMessagePage を使用すると、カスタムドメインモデルとやり取りできることがわかります。例: Message オブジェクトを返すメソッドを公開します:

Java
public Message getMessage() throws ParseException {
    Message message = new Message();
    message.setId(getId());
    message.setCreated(getCreated());
    message.setSummary(getSummary());
    message.setText(getText());
    return message;
}
Kotlin
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())

その後、アサーションでリッチドメインオブジェクトを使用できます。

最後に、テストが完了したら、次のように WebDriver インスタンスを閉じることを忘れないでください。

Java
@AfterEach
void destroy() {
    if (driver != null) {
        driver.close();
    }
}
Kotlin
@AfterEach
fun destroy() {
    if (driver != null) {
        driver.close()
    }
}

WebDriver の使用に関する追加情報については、Selenium WebDriver のドキュメント (GitHub) を参照してください。

高度な MockMvcHtmlUnitDriverBuilder

これまでの例では、Spring TestContext フレームワークによってロードされた WebApplicationContext に基づいて WebDriver を構築することにより、可能な限り最も簡単な方法で MockMvcHtmlUnitDriverBuilder を使用しました。このアプローチは、次のようにここで繰り返されます。

Java
WebDriver driver;

@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin
lateinit var driver: WebDriver

@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}

次のように、追加の構成オプションを指定することもできます。

Java
WebDriver driver;

@BeforeEach
void setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demonstrates applying a MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // for illustration only - defaults to ""
            .contextPath("")
            // By default MockMvc is used for localhost only;
            // the following will use MockMvc for example.com and example.org as well
            .useMockMvcForHosts("example.com","example.org")
            .build();
}
Kotlin
lateinit var driver: WebDriver

@BeforeEach
fun setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demonstrates applying a MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // for illustration only - defaults to ""
            .contextPath("")
            // By default MockMvc is used for localhost only;
            // the following will use MockMvc for example.com and example.org as well
            .useMockMvcForHosts("example.com","example.org")
            .build()
}

別の方法として、次のように MockMvc インスタンスを個別に構成して MockMvcHtmlUnitDriverBuilder に提供することにより、まったく同じセットアップを実行できます。

Java
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

driver = MockMvcHtmlUnitDriverBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

これはより冗長ですが、MockMvc インスタンスを使用して WebDriver を構築することにより、指先で MockMvc の全機能を使用できます。

MockMvc インスタンスの作成に関する追加情報については、セットアップの選択を参照してください。
MockMvc および Geb

前のセクションでは、MockMvc を WebDriver と使用する方法を説明しました。このセクションでは、ゲブ (英語) を使用して、テストを Groovy-er にします。

なぜ Geb と MockMvc なのでしょうか?

Geb は WebDriver によってサポートされているため、WebDriver から得られるのと同じ利点の多くを提供します。ただし、Geb は、ボイラープレートコードの一部を処理することで、作業をさらに簡単にします。

MockMvc と Geb のセットアップ

次のように、MockMvc を使用する Selenium WebDriver で Geb Browser を簡単に初期化できます。

def setup() {
    browser.driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context)
        .build()
}
これは、MockMvcHtmlUnitDriverBuilder を使用する簡単な例です。より高度な使用箇所については、高度な MockMvcHtmlUnitDriverBuilder を参照してください。

これにより、サーバーとして localhost を参照する URL が、実際の HTTP 接続を必要とせずに、MockMvc インスタンスにリダイレクトされるようになります。他の URL は、通常どおりネットワーク接続を使用してリクエストされます。これにより、CDN の使用を簡単にテストできます。

MockMvc と Geb の使用箇所

これで、通常どおり Geb を使用できますが、アプリケーションをサーブレットコンテナーにデプロイする必要はありません。例:次のメッセージを作成するためにビューをリクエストできます:

to CreateMessagePage

次に、フォームに入力して送信し、次のようにメッセージを作成します。

when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)

認識されないメソッド呼び出し、プロパティアクセス、または見つからない参照は、現在のページオブジェクトに転送されます。これにより、WebDriver を直接使用するときに必要な定型コードの多くが削除されます。

直接 WebDriver を使用する場合と同様に、これはページオブジェクトパターンを使用することにより、HtmlUnit テストの設計を改善します。前述のように、HtmlUnit および WebDriver でページオブジェクトパターンを使用できますが、Geb ではさらに簡単です。Groovy ベースの新しい CreateMessagePage 実装を検討してください。

class CreateMessagePage extends Page {
    static url = 'messages/form'
    static at = { assert title == 'Messages : Create'; true }
    static content =  {
        submit { $('input[type=submit]') }
        form { $('form') }
        errors(required:false) { $('label.error, .alert-error')?.text() }
    }
}

CreateMessagePage は Page を継承します。Page の詳細については説明しませんが、要約すると、すべてのページに共通の機能が含まれています。このページが見つかる URL を定義します。これにより、次のようにページに移動できます。

to CreateMessagePage

また、指定されたページにいるかどうかを判断する at クロージャーもあります。正しいページにいる場合は、true が返されます。これが、次のように正しいページにいると断言できる理由です。

then:
at CreateMessagePage
errors.contains('This field is required.')
クロージャーでアサーションを使用して、間違ったページにいた場合に問題が発生した場所を判断できるようにします。

次に、ページ内のすべての関心領域を指定する content クロージャーを作成します。jQuery 風の Navigator API (英語) を使用して、関心のあるコンテンツを選択できます。

最後に、次のように、新しいメッセージが正常に作成されたことを確認できます。

then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage

Geb を最大限に活用する方法の詳細については、ゲブの本 (英語) ユーザーマニュアルを参照してください。

3.6.3. クライアント側の REST テスト

クライアント側のテストを使用して、RestTemplate を内部的に使用するコードをテストできます。予想されるリクエストを宣言し、「スタブ」レスポンスを提供することにより、コードの分離テスト(サーバーを実行せずに)に集中できるようになります。次の例は、その方法を示しています。

Java
RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());

// Test code that uses the above RestTemplate ...

mockServer.verify();
Kotlin
val restTemplate = RestTemplate()

val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess())

// Test code that uses the above RestTemplate ...

mockServer.verify()

上記の例では、MockRestServiceServer (クライアント側の REST テストの中心クラス)は、RestTemplate を、期待に反して実際のリクエストをアサートし、「スタブ」レスポンスを返すカスタム ClientHttpRequestFactory で構成します。この場合、/greeting へのリクエストが予想され、text/plain コンテンツを含む 200 レスポンスを返します。必要に応じて、追加の予想リクエストとスタブレスポンスを定義できます。予想されるリクエストとスタブレスポンスを定義する場合、RestTemplate は通常どおりクライアント側のコードで使用できます。テストの最後に、mockServer.verify() を使用して、すべての期待が満たされていることを確認できます。

デフォルトでは、リクエストは予想が宣言された順序で予想されます。サーバーの構築時に ignoreExpectOrder オプションを設定できます。この場合、すべての期待値が(順番に)チェックされ、特定のリクエストに一致するものが見つかります。つまり、リクエストは任意の順序で送信できます。次の例では、ignoreExpectOrder を使用しています。

Java
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
Kotlin
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()

デフォルトでは、順序付けされていないリクエストでも、各リクエストは一度だけ実行できます。expect メソッドは、カウント範囲を指定する ExpectedCount 引数を受け入れるオーバーロードされたバリアントを提供します(たとえば、oncemanyTimesmaxminbetween など)。次の例では times を使用しています。

Java
RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());

// ...

mockServer.verify();
Kotlin
val restTemplate = RestTemplate()

val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess())
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess())

// ...

mockServer.verify()

ignoreExpectOrder が設定されていない(デフォルト)ため、リクエストが宣言の順序で予期される場合、その順序は予期されるリクエストの最初にのみ適用されることに注意してください。たとえば、「/something」が 2 回、「/somewhere」が 3 回と予想される場合、「/somewhere」へのリクエストがある前に「/something」へのリクエストがあるはずですが、その後の「/something」および「/somewhere 「いつでもリクエストを送信できます。

上記のすべての代替として、クライアント側のテストサポートは、RestTemplate に構成して MockMvc インスタンスにバインドできる ClientHttpRequestFactory 実装も提供します。これにより、サーバーを実行せずに、実際のサーバー側ロジックを使用してリクエストを処理できます。次の例は、その方法を示しています。

Java
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));

// Test code that uses the above RestTemplate ...
Kotlin
val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build()
restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc))

// Test code that uses the above RestTemplate ...
静的インポート

サーバー側のテストと同様に、クライアント側のテスト用の流れるような API には、いくつかの静的インポートが必要です。それらは MockRest* を検索することで簡単に見つけることができます。Eclipse ユーザーは、MockRestRequestMatchers.* および MockRestResponseCreators.* を「Java」→「エディター」→「コンテンツアシスト」→「お気に入り」の Eclipse 設定で「お気に入りの静的メンバー」として追加する必要があります。これにより、静的メソッド名の最初の文字を入力した後にコンテンツアシストを使用できます。他の IDE(IntelliJ など)は、追加の構成を必要としない場合があります。静的メンバーでのコード補完のサポートを確認してください。

クライアント側の REST テストのその他の例

Spring MVC Test 独自のテストには、クライアント側の REST テストのテスト例 (GitHub) が含まれています。

3.7. WebTestClient

WebTestClient は WebClient の周囲の薄いシェルで、これを使用してリクエストを実行し、レスポンスを検証するための専用の流れるような API を公開します。WebTestClient は、モックのリクエストとレスポンスを使用して WebFlux アプリケーションにバインドするか、HTTP 接続を介して任意の Web サーバーをテストできます。

Kotlin ユーザー : WebTestClient の使用に関連するこのセクションを参照してください。

3.7.1. セットアップ

WebTestClient を作成するには、いくつかのサーバーセットアップオプションのいずれかを選択する必要があります。実質的に、WebFlux アプリケーションをバインドするように構成するか、URL を使用して実行中のサーバーに接続します。

コントローラーにバインド

次の例は、一度に 1 つの @Controller をテストするサーバーセットアップを作成する方法を示しています。

Java
client = WebTestClient.bindToController(new TestController()).build();
Kotlin
client = WebTestClient.bindToController(TestController()).build()

上記の例は、WebFlux Java 構成をロードし、指定されたコントローラーを登録します。結果の WebFlux アプリケーションは、モックリクエストおよびレスポンスオブジェクトを使用して、HTTP サーバーなしでテストされます。Builder には、デフォルトの WebFlux Java 構成をカスタマイズするためのメソッドがさらにあります。

ルーター関数にバインド

次の例は、RouterFunction からサーバーをセットアップする方法を示しています。

Java
RouterFunction<?> route = ...
client = WebTestClient.bindToRouterFunction(route).build();
Kotlin
val route: RouterFunction<*> = ...
val client = WebTestClient.bindToRouterFunction(route).build()

内部的に、構成は RouterFunctions.toWebHandler に渡されます。結果の WebFlux アプリケーションは、モックリクエストおよびレスポンスオブジェクトを使用して、HTTP サーバーなしでテストされます。

ApplicationContext にバインド

次の例は、アプリケーションまたはそのサブセットの Spring 構成からサーバーをセットアップする方法を示しています。

Java
@SpringJUnitConfig(WebConfig.class) (1)
class MyTests {

    WebTestClient client;

    @BeforeEach
    void setUp(ApplicationContext context) {  (2)
        client = WebTestClient.bindToApplicationContext(context).build(); (3)
    }
}
1 ロードする構成を指定する
2 構成を注入する
3WebTestClient を作成する
Kotlin
@SpringJUnitConfig(WebConfig::class) (1)
class MyTests {

    lateinit var client: WebTestClient

    @BeforeEach
    fun setUp(context: ApplicationContext) { (2)
        client = WebTestClient.bindToApplicationContext(context).build() (3)
    }
}
1 ロードする構成を指定する
2 構成を注入する
3WebTestClient を作成する

内部的には、構成は WebHttpHandlerBuilder に渡され、リクエスト処理チェーンをセットアップします。詳細については、WebHandler API を参照してください。結果の WebFlux アプリケーションは、モックリクエストおよびレスポンスオブジェクトを使用して、HTTP サーバーなしでテストされます。

サーバーにバインド

次のサーバーセットアップオプションを使用すると、実行中のサーバーに接続できます。

Java
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
Kotlin
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
クライアントビルダー

前述のサーバー設定オプションに加えて、ベース URL、デフォルトヘッダー、クライアントフィルターなどを含むクライアントオプションを構成することもできます。これらのオプションは、bindToServer に従ってすぐに使用できます。他のすべてについては、次のように、configureClient() を使用してサーバーからクライアント構成に移行する必要があります。

Java
client = WebTestClient.bindToController(new TestController())
        .configureClient()
        .baseUrl("/test")
        .build();
Kotlin
client = WebTestClient.bindToController(TestController())
        .configureClient()
        .baseUrl("/test")
        .build()

3.7.2. テストの作成

WebTestClient は、exchange() を使用してリクエストを実行するまで、WebClient と同一の API を提供します。exchange() の後に続くのは、レスポンスを検証するための連鎖 API ワークフローです。

通常、次のように、レスポンスステータスとヘッダーをアサートすることから始めます。

Java
client.get().uri("/persons/1")
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isOk()
            .expectHeader().contentType(MediaType.APPLICATION_JSON)
Kotlin
client.get().uri("/persons/1")
        .accept(MediaType.APPLICATION_JSON)
        .exchange()
        .expectStatus().isOk()
        .expectHeader().contentType(MediaType.APPLICATION_JSON)

次に、レスポンス本文をデコードして使用する方法を指定します。

  • expectBody(Class<T>): 単一のオブジェクトにデコードします。

  • expectBodyList(Class<T>): オブジェクトをデコードして List<T> に収集します。

  • expectBody(): JSON コンテンツまたは空のボディの場合は byte[] にデコードします。

次に、本体に組み込みのアサーションを使用できます。次の例は、そのための 1 つの方法を示しています。

Java
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBodyList(Person.class).hasSize(3).contains(person);
Kotlin
import org.springframework.test.web.reactive.server.expectBodyList

client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBodyList<Person>().hasSize(3).contains(person)

次の例に示すように、組み込みのアサーションを超えて独自のアサーションを作成することもできます。

Java
import org.springframework.test.web.reactive.server.expectBody

client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .consumeWith(result -> {
            // custom assertions (e.g. AssertJ)...
        });
Kotlin
client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody<Person>()
        .consumeWith {
            // custom assertions (e.g. AssertJ)...
        }

次のように、ワークフローを終了して結果を取得することもできます。

Java
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();
Kotlin
import org.springframework.test.web.reactive.server.expectBody

val result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk
        .expectBody<Person>()
        .returnResult()
ジェネリックを使用してターゲットタイプにデコードする必要がある場合は、Class<T> ではなく ParameterizedTypeReference(Javadoc) を受け入れるオーバーロードメソッドを探します。
コンテンツなし

レスポンスにコンテンツが含まれていない場合(または含まれているかどうかは気にしません)は、Void.class を使用します。これにより、リソースが確実に解放されます。次の例は、その方法を示しています。

Java
client.get().uri("/persons/123")
        .exchange()
        .expectStatus().isNotFound()
        .expectBody(Void.class);
Kotlin
client.get().uri("/persons/123")
        .exchange()
        .expectStatus().isNotFound
        .expectBody<Unit>()

または、レスポンスコンテンツがないことをアサートする場合は、次のようなコードを使用できます。

Java
client.post().uri("/persons")
        .body(personMono, Person.class)
        .exchange()
        .expectStatus().isCreated()
        .expectBody().isEmpty();
Kotlin
client.post().uri("/persons")
        .bodyValue(person)
        .exchange()
        .expectStatus().isCreated()
        .expectBody().isEmpty()
JSON コンテンツ

expectBody() を使用すると、レスポンスは byte[] として消費されます。これは、生のコンテンツアサーションに役立ちます。例:次のように、JSONAssert (英語) を使用して JSON コンテンツを検証できます。

Java
client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .json("{\"name\":\"Jane\"}")
Kotlin
client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .json("{\"name\":\"Jane\"}")

次のように、JSONPath (GitHub) 式を使用することもできます。

Java
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$[0].name").isEqualTo("Jane")
        .jsonPath("$[1].name").isEqualTo("Jason");
Kotlin
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$[0].name").isEqualTo("Jane")
        .jsonPath("$[1].name").isEqualTo("Jason")
ストリーミングレスポンス

無限ストリーム("text/event-stream" または "application/stream+json" など)をテストするには、次の例に示すように、レスポンスステータスとヘッダーアサーションの直後に、(returnResult を使用して)チェーン API を終了する必要があります。

Java
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
        .accept(TEXT_EVENT_STREAM)
        .exchange()
        .expectStatus().isOk()
        .returnResult(MyEvent.class);
Kotlin
import org.springframework.test.web.reactive.server.returnResult

val result = client.get().uri("/events")
        .accept(TEXT_EVENT_STREAM)
        .exchange()
        .expectStatus().isOk()
        .returnResult<MyEvent>()

これで、Flux<T> を消費し、デコードされたオブジェクトが来たらアサートし、テストの目的が満たされた時点でキャンセルできます。次の例に示すように、reactor-test モジュールの StepVerifier を使用することをお勧めします。

Java
Flux<Event> eventFlux = result.getResponseBody();

StepVerifier.create(eventFlux)
        .expectNext(person)
        .expectNextCount(4)
        .consumeNextWith(p -> ...)
        .thenCancel()
        .verify();
Kotlin
val eventFlux = result.getResponseBody()

StepVerifier.create(eventFlux)
        .expectNext(person)
        .expectNextCount(4)
        .consumeNextWith { p -> ... }
        .thenCancel()
        .verify()
リクエストボディー

リクエストの作成に関しては、WebTestClient は WebClient と同一の API を提供し、実装はほとんど単純なパススルーです。フォームデータ、マルチパートリクエストなどの送信を含め、本文を含むリクエストを準備する方法の例については、WebClient のドキュメントを参照してください。

4. その他のリソース

テストの詳細については、次のリソースを参照してください。

  • JUnit (英語) : 「プログラマ向けの Java 用テストフレームワーク」。テストスイートで Spring Framework によって使用され、Spring TestContext フレームワークでサポートされています。

  • TestNG (英語) : テストグループ、データ駆動型テスト、分散テスト、およびその他の機能のサポートが追加された、JUnit に触発されたテストフレームワーク。Spring TestContext フレームワークでサポート

  • AssertJ (英語) : Java 8 ラムダ、ストリーム、およびその他の機能のサポートを含む「Java のフルアサーション」。

  • モックオブジェクト (英語) : ウィキペディアの記事。

  • MockObjects.com (英語) : モックオブジェクト専用の Web サイト。テスト駆動開発内のコード設計を改善するための手法です。

  • Mockito (英語) : Test Spy (英語) パターンに基づく Java モックライブラリ。Spring Framework のテストスイートで使用されます。

  • EasyMock (英語) : Java ライブラリ「Java のプロキシメカニズムを使用してオンザフライで生成することにより、インターフェース(およびクラス拡張を介したオブジェクト)のモックオブジェクトを提供します。」

  • JMock (英語) : モックオブジェクトを使用した Java コードのテスト駆動開発をサポートするライブラリ。

  • DbUnit (英語) : JUnit 拡張機能(Ant および Maven でも使用可能)は、データベース駆動型プロジェクトを対象としており、テスト実行の間にデータベースを既知の状態にします。

  • テストコンテナー (英語) : JUnit テストをサポートする Java ライブラリ。軽量で使い捨ての一般的なデータベース、Selenium Web ブラウザー、または Docker コンテナーで実行できるその他のインスタンスを提供します。

  • The Grinder (英語) : Java 負荷テストフレームワーク。

  • SpringMockK (GitHub) : Mockito の代わりに MockK (英語) を使用して Kotlin で記述された Spring Boot 統合テストのサポート。

Unofficial Translation by spring.pleiades.io. See the original content.