上下文層次結構

當編寫依賴於已載入 Spring ApplicationContext 的整合測試時,通常只測試一個上下文就足夠了。然而,有時針對 ApplicationContext 例項層次結構進行測試是有益甚至必要的。例如,如果您正在開發一個 Spring MVC Web 應用程式,通常會有一個由 Spring 的 ContextLoaderListener 載入的根 WebApplicationContext,以及一個由 Spring 的 DispatcherServlet 載入的子 WebApplicationContext。這會形成一個父子上下文層次結構,其中共享元件和基礎設施配置在根上下文(父)中宣告,並在子上下文(子)中被 Web 特定元件使用。另一個用例可以在 Spring Batch 應用程式中找到,其中通常有一個父上下文提供共享批處理基礎設施的配置,以及一個子上下文用於特定批處理作業的配置。

您可以透過在單個測試類或測試類層次結構中,使用 @ContextHierarchy 註解宣告上下文配置來編寫使用上下文層次結構的整合測試。如果在測試類層次結構中的多個類上聲明瞭上下文層次結構,您還可以合併或覆蓋上下文層次結構中特定命名級別的上下文配置。合併層次結構中給定級別的配置時,配置資源型別(即 XML 配置檔案或元件類)必須一致。否則,上下文層次結構中不同級別使用不同資源型別進行配置是完全可以接受的。

如果您在上下文字配置為上下文層次結構一部分的測試中使用了 @DirtiesContext,您可以使用 hierarchyMode 標誌來控制如何清除上下文快取。

更多詳細資訊,請參閱 Spring 測試註解中關於 @DirtiesContext 的討論以及 @DirtiesContext 的 Javadoc。

本節中基於 JUnit Jupiter 的示例展示了需要使用上下文層次結構的整合測試的常見配置場景。

單個測試類使用上下文層次結構

ControllerIntegrationTests 透過宣告一個由兩個級別組成的上下文層次結構,代表了 Spring MVC Web 應用程式的典型整合測試場景:一個用於根 WebApplicationContext(使用 TestAppConfig @Configuration 類載入),一個用於排程程式 Servlet WebApplicationContext(使用 WebConfig @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 的標準語義。SoapWebServiceTestsRestWebServiceTests 都擴充套件了 AbstractWebTests,並使用 @ContextHierarchy 定義了上下文層次結構。結果是載入了三個應用程式上下文(每個 @ContextConfiguration 宣告一個),並且基於 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 定義了層次結構中的兩個級別:parentchildExtendedTests 擴充套件了 BaseTests,並指示 Spring TestContext Framework 合併 child 層次結構級別的上下文配置,透過確保 @ContextConfigurationname 屬性中宣告的名稱都是 child。結果是載入了三個應用程式上下文:一個用於 /app-config.xml,一個用於 /user-config.xml,以及一個用於 {"/user-config.xml", "/order-config.xml"}。與上一個示例一樣,從 /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 覆蓋的上下文層次結構

@ContextHierarchyBean 覆蓋一起使用時,例如 @TestBean@MockitoBean@MockitoSpyBean,可能需要或希望將覆蓋應用於上下文層次結構中的單個級別。為了實現這一點,Bean 覆蓋必須指定一個與透過 @ContextConfiguration 中的 name 屬性配置的名稱匹配的上下文名稱。

以下測試類將第二個層次結構的名稱配置為 "user-config",並同時指定應在名為 "user-config" 的上下文中使用 Mockito spy 包裝 UserService。因此,Spring 只會在 "user-config" 上下文中嘗試建立 spy,而不會在父上下文中嘗試建立 spy。

  • 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 覆蓋例項注入到測試類中以便與它們互動——例如,為 mock 配置 stubbing。然而,@Autowired 總是注入在上下文層次結構最低層找到的匹配 Bean。因此,要從上下文層次結構的特定級別注入 Bean 覆蓋例項,您需要使用適當的 Bean 覆蓋註解標註欄位,並配置上下文級別的名稱。

以下測試類將層次結構級別的名稱配置為 "parent""child"。它還聲明瞭兩個 PropertyService 欄位,這些欄位被配置為在各自名為 "parent""child" 的上下文中建立或替換 PropertyService Bean 為 Mockito mock。因此,來自 "parent" 上下文的 mock 將注入到 propertyServiceInParent 欄位中,而來自 "child" 上下文的 mock 將注入到 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

	// ...
}