Context Hierarchies

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.

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.

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 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

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

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

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
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.