skip to content

How does the TestContext Framework resolve which ContextLoader to use, and what is SmartContextLoader / DelegatingSmartContextLoader?

level: middleimportance: should knowfreq 40%

answer

  1. ContextLoader SPI -> SmartContextLoader (merged model)
  2. default = DelegatingSmartContextLoader
  3. AnnotationConfig (classes) + GenericXml/Groovy (locations)
  4. both classes+locations -> IllegalStateException
  5. @WebAppConfiguration -> WebDelegatingSmartContextLoader

basics

~10 s

If you don't set loader, Spring uses DelegatingSmartContextLoader by default. It delegates to AnnotationConfigContextLoader for classes and GenericXmlContextLoader/GenericGroovyXmlContextLoader for locations, picking whichever matches your declaration.

solid answer

~30 s

`ContextLoader` is the SPI that actually builds the ApplicationContext from a `MergedContextConfiguration`. `SmartContextLoader` is the newer sub-interface that understands both classes and resource locations and can do convention detection. When `@ContextConfiguration` has no explicit `loader`, the framework picks `DelegatingSmartContextLoader`, which wraps `AnnotationConfigContextLoader` (component classes) and `GenericXmlContextLoader` plus `GenericGroovyXmlContextLoader` (XML/Groovy). It delegates to whichever candidate supports the declared config, and if none is declared it runs each loader's detection. With `@WebAppConfiguration` the default becomes `WebDelegatingSmartContextLoader`, producing a `WebApplicationContext`. You can override with an explicit `loader`, but a custom loader replaces this delegation, so you lose automatic classes-vs-locations selection.

code

java · 18 lines
java
// Default: no loader set -> DelegatingSmartContextLoader chooses
// AnnotationConfigContextLoader because only `classes` is present.
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
class DefaultLoaderTest { }

// Explicit loader overrides delegation entirely.
@ExtendWith(SpringExtension.class)
@ContextConfiguration(
        classes = AppConfig.class,
        loader = AnnotationConfigContextLoader.class)
class ExplicitLoaderTest { }

// Web: @WebAppConfiguration flips default to WebDelegatingSmartContextLoader
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
class WebLoaderTest { }

go deeper

for a junior

Just know a ContextLoader builds the context and you rarely set it yourself.

for a middle

Name DelegatingSmartContextLoader and its two candidate loaders and how it picks based on classes vs locations.

for a senior

Explain the SmartContextLoader vs ContextLoader distinction, the ambiguity exception, and the web variant.

for a principal

Relate it to how @SpringBootTest layers SpringBootContextLoader and when a custom AbstractContextLoader is justified.

## The loader SPI `org.springframework.test.context.ContextLoader` is the strategy interface the TestContext Framework uses to **construct** the `ApplicationContext`. The original interface exposed `processLocations(...)` and `loadContext(String... locations)` — string-oriented, XML-era. `org.springframework.test.context.SmartContextLoader` **extends** `ContextLoader` and is the modern replacement. It works against the richer `ContextConfigurationAttributes` / `MergedContextConfiguration` model and adds: - `processContextConfiguration(ContextConfigurationAttributes)` — a hook to *detect* defaults (nested @Configuration classes or convention XML) and normalize declared config. - `loadContext(MergedContextConfiguration)` — build the context from the fully merged config (classes + locations + initializers + profiles + parent). Because it sees the merged model, a `SmartContextLoader` can support **both** component classes and resource locations, which the old `ContextLoader` could not. ## Resolution order When the framework needs a loader for a `@ContextConfiguration`: 1. **Explicit `loader` attribute** — if you set `@ContextConfiguration(loader = MyLoader.class)`, that concrete loader is used, period. 2. **Inherited loader** — if a superclass test declared a loader and `inheritLocations`/inheritance applies, it can be inherited. 3. **Default** — otherwise the framework uses a **delegating** loader: - `DelegatingSmartContextLoader` for standard tests. - `WebDelegatingSmartContextLoader` when `@WebAppConfiguration` is present (yields a `GenericWebApplicationContext`). ## How DelegatingSmartContextLoader delegates `DelegatingSmartContextLoader` internally holds two candidate `SmartContextLoader`s: - `AnnotationConfigContextLoader` — handles **component/@Configuration classes**. - `GenericXmlContextLoader` — handles **XML** resource locations. (`GenericGroovyXmlContextLoader` is also used when Groovy is on the classpath and a `.groovy` resource is present.) For a given declaration it asks each candidate whether it *supports* the config: - If only `classes` are declared -> `AnnotationConfigContextLoader`. - If only `locations` are declared -> the XML/Groovy loader. - If **nothing** is declared -> each candidate runs its detection (nested @Configuration, or `<Test>-context.xml`). **Gotcha:** if a single `@ContextConfiguration` somehow ends up with *both* classes and locations, `DelegatingSmartContextLoader` cannot decide and throws an `IllegalStateException` — it deliberately refuses ambiguous input. This is why you keep one style per level. ## Web variant Adding `@WebAppConfiguration` switches the default to `WebDelegatingSmartContextLoader`, whose candidates are `AnnotationConfigWebContextLoader` and `GenericXmlWebContextLoader`, producing a `WebApplicationContext` with a mock `ServletContext`. ## Custom loaders You can implement `AbstractGenericContextLoader` / `AbstractContextLoader` to customize context creation (e.g., custom `BeanDefinitionReader`). Setting `loader` **replaces** the delegating behavior, so a custom loader that only understands one config style loses the automatic classes-vs-locations dispatch — a common cause of 'my classes attribute is ignored' surprises. ## When it matters in interviews Mostly you never touch this — the defaults 'just work'. It becomes relevant when: writing a custom test infrastructure, debugging why a loader ignores your config, or explaining how `@SpringBootTest` layers `SpringBootContextLoader` on top of this SPI.

  • What happens if a single @ContextConfiguration declares both `classes` and `locations`?
    DelegatingSmartContextLoader treats it as ambiguous and throws an IllegalStateException, because neither of its candidate loaders can claim a config that mixes both styles.
  • How is SmartContextLoader different from the original ContextLoader interface?
    ContextLoader was string/locations oriented (processLocations, loadContext(String...)). SmartContextLoader works against the merged model, supports component classes and locations together, and can perform convention-based default detection.

saying these in an interview costs you the question

  • Saying the default loader is AnnotationConfigContextLoader directly (it's DelegatingSmartContextLoader which delegates to it)
  • Claiming DelegatingSmartContextLoader happily loads both classes and locations at once
  • Not knowing @WebAppConfiguration changes the loader to the web variant

context