上下文层次结构

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

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

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

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

ControllerIntegrationTests 通过声明一个由两个级别组成的上下文层次结构,表示了 Spring MVC Web 应用程序的典型集成测试场景,一个用于根WebApplicationContext(使用TestAppConfig @Configuration 类加载),另一个用于调度程序 servlet WebApplicationContext(使用WebConfig @Configuration 类加载)。自动装配到测试实例中的WebApplicationContext 是子上下文(即层次结构中最低的上下文)的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 框架合并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() {}
在上下文层次结构中弄脏上下文
如果在上下文配置为上下文层次结构一部分的测试中使用@DirtiesContext,则可以使用hierarchyMode 标志来控制如何清除上下文缓存。有关更多详细信息,请参阅Spring 测试注解 中对@DirtiesContext 的讨论以及@DirtiesContext javadoc。