上下文層次結構
當編寫依賴於已載入 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 Framework 合併 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"
,並同時指定應在名為 "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
// ...
}