This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 6.2.5!

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.

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.

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

Context hierarchies with bean overrides

When @ContextHierarchy is used in conjunction with bean overrides such as @TestBean, @MockitoBean, or @MockitoSpyBean, it may be desirable or necessary to have the override applied to a single level in the context hierarchy. To achieve that, the bean override must specify a context name that matches a name configured via the name attribute in @ContextConfiguration.

The following test class configures the name of the second hierarchy level to be "user-config" and simultaneously specifies that the UserService should be wrapped in a Mockito spy in the context named "user-config". Consequently, Spring will only attempt to create the spy in the "user-config" context and will not attempt to create the spy in the parent context.

  • 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

	// ...
}

When applying bean overrides in different levels of the context hierarchy, you may need to have all of the bean override instances injected into the test class in order to interact with them — for example, to configure stubbing for mocks. However, @Autowired will always inject a matching bean found in the lowest level of the context hierarchy. Thus, to inject bean override instances from specific levels in the context hierarchy, you need to annotate fields with appropriate bean override annotations and configure the name of the context level.

The following test class configures the names of the hierarchy levels to be "parent" and "child". It also declares two PropertyService fields that are configured to create or replace PropertyService beans with Mockito mocks in the respective contexts, named "parent" and "child". Consequently, the mock from the "parent" context will be injected into the propertyServiceInParent field, and the mock from the "child" context will be injected into the propertyServiceInChild field.

  • 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

	// ...
}