テストでの Bean のオーバーライド
テストでの Bean オーバーライドとは、テストクラスまたはテストクラス内の 1 つ以上の非静的フィールドにアノテーションを付けることで、テストクラスの ApplicationContext 内の特定の Bean をオーバーライドする機能を指します。
この機能は、DefaultListableBeanFactory setAllowBeanDefinitionOverriding フラグを true に設定して @Bean 経由で Bean を登録する方法よりもリスクの少ない代替手段として意図されています。 |
Spring TestContext フレームワークは、Bean オーバーライド用のアノテーションを 2 セット提供します。
前者は純粋に Spring に依存し、後者のセットは Mockito (英語) サードパーティライブラリに依存します。
カスタム Bean オーバーライドサポート
上記の 3 つのアノテーションは、@BeanOverride メタアノテーションと関連インフラストラクチャに基づいて構築されており、カスタム Bean オーバーライドバリアントを定義できます。
カスタム Bean オーバーライドサポートを実装するには、次のものが必要です。
使用する
BeanOverrideProcessorを定義する@BeanOverrideでアノテーションされたアノテーションメタカスタム
BeanOverrideProcessor実装プロセッサーによって作成された 1 つ以上の具体的な
BeanOverrideHandler実装
Spring TestContext フレームワークには、Bean オーバーライドをサポートし、インフラストラクチャの残りの部分を設定する次の API の実装が含まれています。
BeanFactoryPostProcessorContextCustomizerFactoryTestExecutionListener
spring-test モジュールは、後者の 2 つ (BeanOverrideContextCustomizerFactory と BeanOverrideTestExecutionListener) の実装を META-INF/spring.factories プロパティファイル [GitHub] (英語) に登録します。
Bean オーバーライドインフラストラクチャは、テストクラスのアノテーションと、@BeanOverride でメタアノテーションされたテストクラス内の非静的フィールドのアノテーションを検索し、適切な BeanOverrideHandler を作成するための対応する BeanOverrideProcessor をインスタンス化します。
次に、内部の BeanOverrideBeanFactoryPostProcessor は Bean オーバーライドハンドラーを使用して、対応する BeanOverrideStrategy で定義されているように Bean を作成、置換、ラップすることにより、テストの ApplicationContext を変更します。
REPLACEBean を置き換えます。対応する Bean が存在しない場合は例外をスローします。
REPLACE_OR_CREATEBean が存在する場合は置き換えます。対応する Bean が存在しない場合は、新しい Bean を作成します。
WRAPオリジナルの Bean を取り出し、折り返しします。
非シングルトン Bean を置き換える場合、非シングルトン Bean は、該当する
|
Spring のオートワイヤーメカニズム (たとえば、 通常、Bean は
|
Bean オーバーライドと Spring AOP プロキシ
Spring ApplicationContext 内の Bean は、@Transactional、@Cacheable、@Retryable のセマンティクスをサポートするために、AOP プロキシでラップされることがよくあります。オーバーライドされた Bean がそのようなプロキシを保持するかどうかは、オーバーライドを作成するために使用された BeanOverrideStrategy に依存します。
REPLACEまたはREPLACE_OR_CREATE戦略を使用するオーバーライド(@TestBeanや@MockitoBeanなど)は、オーバーライドインスタンスを手動シングルトンとして直接登録するため、コンテナーの通常の Bean 後処理がバイパスされます。その結果、オーバーライドインスタンスはベアオブジェクトとなり、元の Bean に適用されるはずの AOP アドバイス(@Transactional、@Cacheable、@Retryable、メソッドセキュリティなど)は一切適用されません。WRAP戦略を使用するオーバーライド(@MockitoSpyBeanなど)は、コンテナーの残りのポストプロセッサー(AOP プロキシの作成を担当するものを含む)が実行される前に、元の Bean への早期参照を取得し、それを使用してオーバーライドインスタンスを作成します。元の Bean がプロキシ化されていた場合、そのプロキシは作成されますが、元の Bean ではなくオーバーライドインスタンスをラップします。ApplicationContextに最終的に含まれ、連携する Bean やテストクラスに注入される Bean は、オーバーライドインスタンスをターゲットとする AOP プロキシであり、オーバーライドインスタンスそのものではありません。
以下の図は、注入された Bean のメソッドを呼び出す呼び出し元の視点から見た、各戦略における Bean の最終的な形状を示しています。
REPLACE または REPLACE_OR_CREATE 戦略では、AOP プロキシは一切存在せず、呼び出し元はオーバーライドインスタンスを直接呼び出します。
caller
│
▼
[ override instance ]WRAP 戦略では、通常であれば元の Bean をラップするはずだった AOP プロキシは引き続き作成されますが、代わりにオーバーライドインスタンスをラップするようになります。
caller
│
▼
[ AOP proxy ] (for example, retry, caching, or transaction advice)
│
│ delegates to its target
▼
[ override instance ] (for example, a Mockito spy created by @MockitoSpyBean)@MockitoSpyBean のような WRAP ベースのオーバーライドの場合、AOP プロキシによって実行される「折り返し」は、結果として生成される Mockito スパイ自体が、作成元の Bean インスタンスを「ラップ」する方法とは無関連です。上記のプロキシは、呼び出し元が実際にどのオブジェクトを呼び出すかを決定しますが、スパイと元のインスタンスとの関連は、スタブ化されていないメソッドがスパイ上で呼び出されたときに何が起こるかのみを決定します。つまり、そのインスタンスの実際の動作にフォールスルーします。
この違いは、Bean のオーバーライドを Mockito のスタブ化および検証 API と組み合わせる際に、実際的な影響を及ぼします。詳細は @MockitoSpyBean および Spring AOP プロキシを参照してください。