コンテキストの階層

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

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

コンテキストがコンテキスト階層の一部として構成されているテストで @DirtiesContext を使用する場合は、hierarchyMode フラグを使用してコンテキストキャッシュをクリアする方法を制御できます。

詳細については、Spring Test アノテーションの @DirtiesContext の説明と @DirtiesContext (Javadoc) の javadoc を参照してください。

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

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

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

  • Java

  • Kotlin

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

	@Autowired
	WebApplicationContext wac;

	// ...
}
@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

  • Kotlin

@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 {}
@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

  • Kotlin

@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 {}
@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

  • Kotlin

@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 {}
@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() {}

Bean オーバーライドによるコンテキスト階層

@ContextHierarchy を @TestBean@MockitoBean@MockitoSpyBean などの Bean オーバーライドと組み合わせて使用する場合、オーバーライドをコンテキスト階層の単一レベルに適用することが望ましい、または必要となる場合があります。そのためには、Bean オーバーライドで、@ContextConfiguration の name 属性で設定された名前と一致するコンテキスト名を指定する必要があります。

以下のテストクラスは、2 番目の階層レベルの名前を "user-config" に設定し、同時に UserService を "user-config" というコンテキスト内の Mockito スパイにラップするよう指定しています。その結果、Spring は "user-config" コンテキスト内でのみスパイの作成を試み、親コンテキスト内ではスパイの作成を試みません。

  • Java

  • Kotlin

@ExtendWith(SpringExtension.class)
@ContextHierarchy({
	@ContextConfiguration(classes = AppConfig.class),
	@ContextConfiguration(classes = UserConfig.class, name = "user-config")
})
class IntegrationTests {

	@MockitoSpyBean(contextName = "user-config")
	UserService userService;

	// ...
}
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
	ContextConfiguration(classes = [AppConfig::class]),
	ContextConfiguration(classes = [UserConfig::class], name = "user-config"))
class IntegrationTests {

	@MockitoSpyBean(contextName = "user-config")
	lateinit var userService: UserService

	// ...
}

コンテキスト階層の異なるレベルで Bean オーバーライドを適用する場合、モックのスタブを設定するなど、Bean オーバーライドインスタンスをすべてテストクラスに注入して操作する必要がある場合があります。ただし、@Autowired は常に、コンテキスト階層の最下位レベルにある一致する Bean を注入します。コンテキスト階層の特定のレベルから Bean オーバーライドインスタンスを注入するには、適切な Bean オーバーライドアノテーションをフィールドに付与し、コンテキストレベルの名前を設定する必要があります。

以下のテストクラスでは、階層レベルの名前を "parent" と "child" に設定しています。また、2 つの PropertyService フィールドを宣言し、それぞれのコンテキストで PropertyService Bean を Mockito モック("parent" および "child")に作成または置換するように設定しています。その結果、"parent" コンテキストのモックは propertyServiceInParent フィールドに、"child" コンテキストのモックは propertyServiceInChild フィールドに注入されます。

  • Java

  • Kotlin

@ExtendWith(SpringExtension.class)
@ContextHierarchy({
	@ContextConfiguration(classes = ParentConfig.class, name = "parent"),
	@ContextConfiguration(classes = ChildConfig.class, name = "child")
})
class IntegrationTests {

	@MockitoBean(contextName = "parent")
	PropertyService propertyServiceInParent;

	@MockitoBean(contextName = "child")
	PropertyService propertyServiceInChild;

	// ...
}
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
	ContextConfiguration(classes = [ParentConfig::class], name = "parent"),
	ContextConfiguration(classes = [ChildConfig::class], name = "child"))
class IntegrationTests {

	@MockitoBean(contextName = "parent")
	lateinit var propertyServiceInParent: PropertyService

	@MockitoBean(contextName = "child")
	lateinit var propertyServiceInChild: PropertyService

	// ...
}