上下文层次结构
当编写依赖于已加载 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
// ...
}