Context Hierarchies

在编写依赖于已加载 Spring ApplicationContext 的集成测试时,通常针对单个上下文进行测试就足够了。但是,有时针对 ApplicationContext 实例的层次结构进行测试是有益的甚至是必要的。例如,如果正在开发一个 Spring MVC Web 应用程序,则通常会加载由 Spring 的 ContextLoaderListener 加载的根 WebApplicationContext 和由 Spring 的 DispatcherServlet 加载的子 WebApplicationContext。这将导致一个父级-子级上下文层次结构,在该层次结构中,共享组件和基础设施配置在根上下文中声明,并在子上下文中由特定于 Web 的组件使用。Spring Batch 应用程序也可以找到另一个用例,在该应用程序中,通常有一个提供共享批处理基础设施配置的父上下文以及用于特定批处理作业的配置的子上下文。

When writing integration tests that rely on a loaded Spring ApplicationContext, it is often sufficient to test against a single context. However, there are times when it is beneficial or even necessary to test against a hierarchy of ApplicationContext instances. For example, if you are developing a Spring MVC web application, you typically have a root WebApplicationContext loaded by Spring’s ContextLoaderListener and a child WebApplicationContext loaded by Spring’s DispatcherServlet. This results in a parent-child context hierarchy where shared components and infrastructure configuration are declared in the root context and consumed in the child context by web-specific components. Another use case can be found in Spring Batch applications, where you often have a parent context that provides configuration for shared batch infrastructure and a child context for the configuration of a specific batch job.

可以通过使用 @ContextHierarchy 注解在单个测试类或测试类层次结构中声明上下文配置来编写使用上下文层次结构的集成测试。如果在测试类层次结构中的多个类上声明上下文层次结构,则还可以合并或覆盖上下文层次结构中特定命名级别的上下文配置。在合并层次结构中给定级别的配置时,配置资源类型(即 XML 配置文件或组件类)必须一致。否则,完全可以接受使用不同的资源类型配置上下文层次结构中的不同级别。

You can write integration tests that use context hierarchies by declaring context configuration with the @ContextHierarchy annotation, either on an individual test class or within a test class hierarchy. If a context hierarchy is declared on multiple classes within a test class hierarchy, you can also merge or override the context configuration for a specific, named level in the context hierarchy. When merging configuration for a given level in the hierarchy, the configuration resource type (that is, XML configuration files or component classes) must be consistent. Otherwise, it is perfectly acceptable to have different levels in a context hierarchy configured using different resource types.

本节中其余基于 JUnit Jupiter 的示例展示了需要使用上下文层次结构的集成测试的常见配置场景。

The remaining JUnit Jupiter based examples in this section show common configuration scenarios for integration tests that require the use of context hierarchies.

带有上下文层次结构的单个测试类

Single test class with context hierarchy

ControllerIntegrationTests 表示 Spring MVC Web 应用程序的典型集成测试场景,它声明一个由两个级别组成的上下文层次结构,它一个用于根 WebApplicationContext(通过使用 TestAppConfig @Configuration 类加载),另一个用于分发器 servlet WebApplicationContext(通过使用 WebConfig @Configuration 类加载)。自动注入测试实例的 WebApplicationContext 是子上下文的一个(即层次结构中最低的上下文)。以下清单显示了此配置场景:

ControllerIntegrationTests represents a typical integration testing scenario for a Spring MVC web application by declaring a context hierarchy that consists of two levels, one for the root WebApplicationContext (loaded by using the TestAppConfig @Configuration class) and one for the dispatcher servlet WebApplicationContext (loaded by using the WebConfig @Configuration class). The WebApplicationContext that is autowired into the test instance is the one for the child context (that is, the lowest context in the hierarchy). The following listing shows this configuration scenario:

  • 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

	// ...
}

带有隐式父上下文的类层次结构

Class hierarchy with implicit parent context

此示例中的测试类在测试类层次结构中定义了上下文层次结构。AbstractWebTests 声明 Spring 支持的 Web 应用程序中根 WebApplicationContext 的配置。不过,请注意,AbstractWebTests 不声明 @ContextHierarchy。因此,AbstractWebTests 的子类可以选择参与上下文层次结构或遵循 @ContextConfiguration 的标准语义。SoapWebServiceTestsRestWebServiceTests 都通过使用 @ContextHierarchy 扩展 AbstractWebTests 并定义上下文层次结构。结果是加载了三个应用程序上下文(一个用于每个 @ContextConfiguration 声明),并且基于 AbstractWebTests 中的配置加载的应用程序上下文被设置为为具体子类加载的每个上下文的父上下文。以下清单显示了此配置场景:

The test classes in this example define a context hierarchy within a test class hierarchy. AbstractWebTests declares the configuration for a root WebApplicationContext in a Spring-powered web application. Note, however, that AbstractWebTests does not declare @ContextHierarchy. Consequently, subclasses of AbstractWebTests can optionally participate in a context hierarchy or follow the standard semantics for @ContextConfiguration. SoapWebServiceTests and RestWebServiceTests both extend AbstractWebTests and define a context hierarchy by using @ContextHierarchy. The result is that three application contexts are loaded (one for each declaration of @ContextConfiguration), and the application context loaded based on the configuration in AbstractWebTests is set as the parent context for each of the contexts loaded for the concrete subclasses. The following listing shows this configuration scenario:

  • 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()

带有合并上下文层次结构配置的类层次结构

Class hierarchy with merged context hierarchy configuration

此示例中的类展示了如何使用命名的层次结构级别来合并上下文层次结构中特定级别的配置。BaseTests 在层次结构中定义两个级别,即 parentchildExtendedTests 扩展了 BaseTests 并指示 Spring TestContext 框架合并 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"} 加载的上下文的父上下文。以下清单显示了此配置场景:

The classes in this example show the use of named hierarchy levels in order to merge the configuration for specific levels in a context hierarchy. BaseTests defines two levels in the hierarchy, parent and child. ExtendedTests extends BaseTests and instructs the Spring TestContext Framework to merge the context configuration for the child hierarchy level, by ensuring that the names declared in the name attribute in @ContextConfiguration are both child. The result is that three application contexts are loaded: one for /app-config.xml, one for /user-config.xml, and one for {"/user-config.xml", "/order-config.xml"}. As with the previous example, the application context loaded from /app-config.xml is set as the parent context for the contexts loaded from /user-config.xml and {"/user-config.xml", "/order-config.xml"}. The following listing shows this configuration scenario:

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

带有覆盖上下文层次结构配置的类层次结构

Class hierarchy with overridden context hierarchy configuration

与前一个示例相反,此示例演示了如何通过将 @ContextConfiguration 中的 inheritLocations 标志设置为 false 来覆盖上下文层次结构中给定命名级别的配置。因此,ExtendedTests 的应用程序上下文仅从 /test-user-config.xml 加载,并将其父级设置为从 /app-config.xml 加载的上下文。以下清单显示了此配置场景:

In contrast to the previous example, this example demonstrates how to override the configuration for a given named level in a context hierarchy by setting the inheritLocations flag in @ContextConfiguration to false. Consequently, the application context for ExtendedTests is loaded only from /test-user-config.xml and has its parent set to the context loaded from /app-config.xml. The following listing shows this configuration scenario:

  • 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() {}
Dirtying a context within a context hierarchy

如果您在上下文中配置为上下级关系的一部分的测试中使用 @DirtiesContext,则可以使用 hierarchyMode 标记控制如何清除上下文缓存。有关进一步的详细信息,请参阅 Spring Testing Annotations@DirtiesContext 的讨论和 @DirtiesContext javadoc。

Dirtying a context within a context hierarchy

If you use @DirtiesContext in a test whose context is configured as part of a context hierarchy, you can use the hierarchyMode flag to control how the context cache is cleared. For further details, see the discussion of @DirtiesContext in Spring Testing Annotations and the @DirtiesContext javadoc.