上下文層次結構
在編寫依賴於已載入 Spring ApplicationContext 的整合測試時,通常只需針對單個上下文進行測試即可。然而,有時針對 ApplicationContext 例項的層次結構進行測試是有益的,甚至是必要的。例如,如果您正在開發一個 Spring MVC Web 應用程式,您通常會有一個由 Spring 的 ContextLoaderListener 載入的根 WebApplicationContext,以及一個由 Spring 的 DispatcherServlet 載入的子 WebApplicationContext。這會產生一個父子上下文層次結構,其中共享元件和基礎設施配置在根上下文中宣告,並由 Web 特定元件在子上下文中消費。另一個用例可以在 Spring Batch 應用程式中找到,您通常有一個父上下文,為共享批處理基礎設施提供配置,以及一個子上下文用於特定批處理作業的配置。
您可以透過在單個測試類或測試類層次結構中宣告帶有 @ContextHierarchy 註解的上下文配置來編寫使用上下文層次結構的整合測試。如果在測試類層次結構中的多個類上聲明瞭上下文層次結構,您還可以合併或覆蓋上下文層次結構中特定命名級別的上下文配置。當合並給定層次結構級別的配置時,配置資源型別(即 XML 配置檔案或元件類)必須保持一致。否則,上下文層次結構中的不同級別使用不同的資源型別進行配置是完全可以接受的。
|
如果您在上下文中作為上下文層次結構的一部分進行配置的測試中使用 有關更多詳細資訊,請參閱 Spring 測試註解 中對 |
本節中基於 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 的標準語義。SoapWebServiceTests 和 RestWebServiceTests 都擴充套件了 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 定義了層次結構中的兩個級別:parent 和 child。ExtendedTests 擴充套件了 BaseTests,並指示 Spring TestContext 框架合併 child 層次結構級別的上下文配置,透過確保 @ContextConfiguration 中 name 屬性中宣告的名稱都是 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 覆蓋的上下文層次結構
當 @ContextHierarchy 與 Bean 覆蓋(例如 @TestBean、@MockitoBean 或 @MockitoSpyBean)一起使用時,可能需要將覆蓋應用於上下文層次結構中的單個級別。為了實現這一點,Bean 覆蓋必須指定一個與透過 @ContextConfiguration 中的 name 屬性配置的名稱匹配的上下文名稱。
以下測試類將第二個層次結構級別的名稱配置為 "user-config",並同時指定 UserService 應該在名為 "user-config" 的上下文中封裝在一個 Mockito spy 中。因此,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 覆蓋例項注入到測試類中才能與它們互動 — 例如,配置模擬的存根。但是,@Autowired 總是會注入在上下文層次結構最低級別找到的匹配 Bean。因此,要從上下文層次結構中的特定級別注入 Bean 覆蓋例項,您需要使用適當的 Bean 覆蓋註解註釋欄位並配置上下文級別的名稱。
以下測試類將層次結構級別的名稱配置為 "parent" 和 "child"。它還聲明瞭兩個 PropertyService 欄位,這些欄位配置為在各自的上下文中(名為 "parent" 和 "child")建立或替換 PropertyService Bean。因此,來自 "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
// ...
}