What is @ContextHierarchy and when would you use parent/child ApplicationContexts in tests?
answer
- array of @ContextConfiguration = levels
- parent first, child last
- child sees parent beans, not the reverse
- name = merge/override level across test subclasses
- each level cached independently; DirtiesContext hierarchyMode
basics
~10 s@ContextHierarchy declares multiple @ContextConfiguration levels that form a parent-child ApplicationContext chain. Child contexts can see parent beans but not vice versa — useful for splitting shared infrastructure (parent) from a web layer (child).
solid answer
~40 s@ContextHierarchy lets a test build a hierarchy of ApplicationContexts instead of a single flat one. You give it an array of @ContextConfiguration, each with a `name`, and they load as parent -> child. Beans in a child context can @Autowire beans from ancestors, but parents cannot see child beans — mirroring Spring's runtime root-vs-servlet context split. Common uses: a shared parent holding datasource/service infra reused by several tests, with a lightweight web/child context on top; or reproducing a real dispatcher-servlet layering. The `name` attribute is key for inheritance: a subclass test can add a new level or, by reusing a parent's `name`, merge/override that level's config. Each level is cached independently, so a shared parent context is built once and reused across child variations, improving suite performance.
code
java · 16 lines// Base test defines the shared root level once.
@ExtendWith(SpringExtension.class)
@ContextHierarchy(
@ContextConfiguration(name = "root", classes = RootConfig.class))
abstract class AbstractRootTest {
@Autowired PaymentService paymentService; // from root
}
// Subclass layers a child level; reuses cached root.
@ContextHierarchy(
@ContextConfiguration(name = "child", classes = WebConfig.class))
class CheckoutWebTest extends AbstractRootTest {
@Autowired CheckoutController controller; // child bean
// controller can inject paymentService from the parent root context;
// beans in RootConfig cannot inject CheckoutController.
}go deeper
Awareness only: it makes parent/child contexts instead of one flat context.
Explain the child-sees-parent visibility rule and a use case like root+servlet web layering.
Discuss the name attribute for cross-subclass level merging and per-level caching for shared parents.
Reason about DirtiesContext hierarchyMode, cache-key composition per level, and when the added complexity is justified vs a flat context.
## The problem it solves A plain `@ContextConfiguration` produces **one flat `ApplicationContext`**. But real Spring apps often run a **context hierarchy**: a **root** `WebApplicationContext` (services, repositories, datasource) as parent, and a **servlet** `WebApplicationContext` (controllers, view resolvers) as child. Child contexts can resolve beans from their parent; parents cannot see child beans. `@ContextHierarchy` lets integration tests reproduce this layering. ## Syntax `@ContextHierarchy` wraps an **array of `@ContextConfiguration`**, each representing one **level**, ordered **outermost parent first, innermost child last**: ```java @ContextHierarchy({ @ContextConfiguration(name = "root", classes = RootConfig.class), @ContextConfiguration(name = "child", classes = WebConfig.class) }) ``` Each `@ContextConfiguration` behaves normally (classes vs locations, initializers, its own loader) but only configures **its** level. ## Visibility rules - A bean in the **child** level can be `@Autowired` from any **ancestor** level. - A bean in a **parent** cannot see or inject a **child** bean. - Bean **name collisions** don't error across levels: the child's definition shadows the parent's for lookups within the child, but the parent still holds its own instance. ## The `name` attribute and inheritance The `name` on each `@ContextConfiguration` identifies a level so it can be **merged/overridden across a test class hierarchy**: - A **subclass** test with its own `@ContextHierarchy` whose level reuses a parent's `name` **merges** additional config into that same level (subject to `inheritLocations`). - Using a **new** `name` **adds** a new level. - This lets a base test class define the `root` level once and many subclasses layer different `child` levels on top, all **sharing the cached root context**. ## Caching implications Each level in the hierarchy has its **own** `MergedContextConfiguration` (which records its parent), so each level is **cached independently**. A `root` context shared by ten child variations is built **once**. `@DirtiesContext(hierarchyMode = ...)` controls whether dirtying evicts just the current level (`CURRENT_LEVEL`) or the whole subtree (`EXHAUSTIVE`). ## Typical use cases 1. **Web layering** — reproduce root+servlet contexts in a MockMvc test. 2. **Shared infra parent** — expensive datasource/embedded-broker beans in a parent reused across many child contexts. 3. **Modular boundaries** — validate that a module only depends on beans it should see (parent) and not on sibling child beans. ## Gotchas - **Ordering matters**: first element is the top-most parent. Getting it backwards inverts visibility. - Omitting `name` still works for a single-class hierarchy, but you **need** names to merge levels across an inheritance chain. - A child re-declaring a bean does not mutate the parent's instance — two instances can coexist, which surprises people expecting an override. - Profiles/initializers are per-level; activating a profile on the child doesn't retroactively affect the parent's already-built beans. ## When NOT to use it Most tests need a single flat context — reach for `@ContextHierarchy` only when you genuinely need parent/child isolation or want to share an expensive parent across many child configs. Over-using it complicates the mental model and the cache keys.
- Can a bean in the parent context inject a bean defined only in the child context?No. Visibility flows one way: children see ancestor beans, but a parent context has no knowledge of its children's bean definitions, so such an injection fails.
- What is the purpose of the `name` attribute on each @ContextConfiguration level?It identifies a level so subclass tests can merge additional config into the same level (reuse the name) or add a new level (new name), enabling shared parent contexts across an inheritance chain.
- How does @DirtiesContext interact with a context hierarchy?Its hierarchyMode chooses eviction scope: CURRENT_LEVEL removes only the current context, while EXHAUSTIVE (default for hierarchies) evicts the current context and the whole subtree of contexts sharing it.
saying these in an interview costs you the question
- Saying parent contexts can access child beans (visibility is child->parent only)
- Listing levels child-first instead of parent-first
- Assuming a child bean with the same name replaces the parent's instance globally rather than shadowing only within the child