skip to content

Spring TestContext Framework

The framework underneath every Spring test: how a context is configured and loaded, how it is cached across test classes, the execution listeners, test profiles and properties, and the JUnit 5 integration. Understanding the cache is what makes a test suite fast.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

25

What is the Spring TestContext application-context cache, and why does it exist?

level: juniorimportance: must knowfreq 60%

answer

  1. Static JVM-wide context cache
  2. Build once, reuse across test classes
  3. Keyed on merged config
  4. Startup is the expensive part
  5. Default 32, LRU

basics

~10 s

Spring starts the application context once and reuses (caches) it across test classes that ask for the same configuration, instead of rebuilding it per test. This makes the whole test suite much faster.

solid answer

~40 s

The Spring TestContext Framework caches loaded ApplicationContexts across the entire test suite (per JVM/test run) so a context is built once and reused by every test that requests an identical configuration. Building a Spring context — component scanning, bean creation, autoconfiguration, connecting to a database — is expensive, so without caching every test class would pay that cost again. The cache is keyed on the merged test configuration, so two test classes with the same @ContextConfiguration/@SpringBootTest classes, profiles, and properties share one context. The cache is static and lives for the duration of the test run; it is not cleared between test classes unless something forces eviction (e.g. @DirtiesContext). Understanding it is the difference between a suite that runs in seconds and one that reloads Spring dozens of times.

code

java · 13 lines
java
// Both test classes below resolve to the SAME cache key
// (same config class, no profiles/props/mocks differences)
// => Spring builds ONE context and reuses it for both.

@SpringJUnitConfig(AppConfig.class)
class OrderServiceTest {
    @Test void placesOrder() { /* ... */ }
}

@SpringJUnitConfig(AppConfig.class)
class InventoryServiceTest {
    @Test void reserves() { /* ... */ } // reuses cached context
}

go deeper

for a junior

Know the one-liner: Spring caches and reuses the context across tests for speed instead of rebuilding it each time.

for a middle

Explain that reuse is keyed on the merged configuration and that shared singletons can cause state leakage between tests.

for a senior

Discuss the ContextCache/CacheAwareContextLoaderDelegate mechanics, default maxSize 32, LRU eviction, and how to keep the number of distinct contexts small.

for a principal

Frame cache design as a suite-wide performance and isolation contract: minimize distinct keys, standardize base test config, reserve @DirtiesContext for genuine corruption.

## The problem it solves Starting a Spring `ApplicationContext` is slow: Spring scans for `@Component`/`@Configuration` classes, instantiates and wires all beans, runs Spring Boot autoconfiguration, opens connection pools, runs Flyway/Liquibase, etc. If every `@SpringBootTest` or `@SpringJUnitConfig` test class rebuilt the context from scratch, a suite of 200 test classes could restart Spring 200 times. ## What the cache is The **Spring TestContext Framework (TCF)** keeps a **static, JVM-wide cache** of loaded contexts, managed by `org.springframework.test.context.cache.ContextCache` (default impl `DefaultContextCache`) and coordinated by the `CacheAwareContextLoaderDelegate`. When a test needs a context, the framework computes a **cache key** from the test's *merged* configuration (`MergedContextConfiguration`). If a context with that key already exists, it is **reused**; otherwise a new one is built and **stored** under that key. Because the cache is static, it survives across test classes within the same test run/JVM — that is the whole point. Two unrelated test classes that resolve to the **same** configuration will share **one physical context instance** (same singleton beans, same connection pool). ## Why it matters day to day - **Speed:** the dominant lever on suite runtime. A well-cached suite may load only a handful of distinct contexts total. - **Correctness:** because the *same* context (and its singleton beans) is shared, tests that mutate shared bean state or global data can leak into each other. Spring provides `@DirtiesContext` to force eviction when a test genuinely corrupts the context. - **Cache misses hurt:** anything that changes the cache key (different `@ActiveProfiles`, different `@TestPropertySource`, adding `@MockBean`/`@MockitoBean`, `@DynamicPropertySource`) produces a *new* context, multiplying startup cost. A common performance regression is accidentally creating many near-identical contexts. ## Key facts - Enabled automatically — you don't turn it on. - Default maximum number of cached contexts is **32**, evicted **LRU** (`spring.test.context.cache.maxSize`). - Rebuilding is triggered only by a new cache key or by explicit eviction (`@DirtiesContext`). ## When to think about it Any time your suite feels slow, or a test mysteriously passes alone but fails in the suite (shared-state leak), the context cache is the first thing to reason about.

  • Is the cache per test class or shared across the whole suite?
    Shared across the whole test run within a JVM. It's a static cache, so any test class in the run whose merged configuration produces the same key reuses the already-loaded context.
  • A test passes in isolation but fails when run with the suite. How does the cache explain that?
    The failing test likely shares a cached context (and its singleton beans / DB state) with an earlier test that mutated shared state. The fix is proper cleanup, transactional rollback, or @DirtiesContext on the offending test.

saying these in an interview costs you the question

  • Thinking Spring rebuilds the context for every test method or every test class
  • Believing the cache is per-thread or per-class rather than a static JVM-wide cache
  • Not knowing that cached contexts share singleton bean state, so tests can leak into each other

context

open as a page

What does @ContextConfiguration do, and how do its `classes` and `locations` attributes differ?

level: juniorimportance: must knowfreq 70%

basics

~10 s

@ContextConfiguration tells the Spring TestContext Framework how to build the ApplicationContext for a test. classes points to @Configuration/component classes; locations (or value) points to XML or Groovy resource files.

open as a page

What is @ExtendWith(SpringExtension.class), and what does adding it to a JUnit 5 test class actually do?

level: juniorimportance: must knowfreq 70%

basics

~10 s

SpringExtension is the JUnit 5 hook that plugs Spring's TestContext into your test. Adding @ExtendWith(SpringExtension.class) lets Spring build an ApplicationContext and inject beans (e.g. via @Autowired) into the test.

open as a page

What does @ActiveProfiles do in a Spring integration test, and how do you use it?

level: juniorimportance: must knowfreq 70%

basics

~10 s

@ActiveProfiles activates one or more Spring bean-definition profiles for a test's ApplicationContext. You put it on the test class, e.g. @ActiveProfiles("test"), so beans and property files tied to that profile are loaded.

open as a page

What attributes make up the TestContext cache key, and what causes two tests to NOT share a context?

level: middleimportance: must knowfreq 55%

basics

~10 s

The key is the merged test configuration: the config classes/locations, active profiles, property sources and inlined properties, context initializers, the context loader, and context customizers (like added mocks). Any difference means a separate context.

open as a page

In @TestPropertySource, what's the difference between the properties/value attributes and the locations attribute, and which wins when both are used?

level: middleimportance: must knowfreq 60%

basics

~20 s

locations points to property files (e.g. "classpath:test.properties"); properties (or value) lists inlined key=value pairs directly in the annotation. When a key appears in both, the inlined properties win — they're applied last with higher precedence.

open as a page

What does @DirtiesContext do, and how do its methodMode and classMode control when the context is evicted?

level: seniorimportance: must knowfreq 50%

basics

~20 s

@DirtiesContext marks the cached context as corrupted so Spring closes and removes it, forcing a fresh one to be built. methodMode controls timing when it's on a test method (default AFTER_METHOD); classMode controls timing when it's on a class (default AFTER_CLASS).

open as a page

A developer adds @TestExecutionListeners with a single custom listener and suddenly @Autowired stops working in the test. Why, and how does mergeMode fix it?

level: seniorimportance: must knowfreq 45%

basics

~10 s

Declaring @TestExecutionListeners with explicit listeners REPLACES the defaults by default, so DependencyInjectionTestExecutionListener is gone and injection breaks. Set mergeMode = MERGE_WITH_DEFAULTS to keep the defaults and add yours.

open as a page

What is a TestExecutionListener in the Spring TestContext Framework, and what do the default listeners give you out of the box?

level: juniorimportance: should knowfreq 40%

basics

~20 s

A TestExecutionListener is a plugin that Spring calls around your tests (e.g. before/after each test method). Default listeners auto-provide dependency injection, @Transactional rollback, @Sql script execution, and @DirtiesContext handling — you get them for free.

open as a page

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

level: middleimportance: should knowfreq 40%

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.

open as a page

Walk through the TestExecutionListener callback lifecycle. What is the difference between prepareTestInstance, beforeTestMethod, and beforeTestExecution, and when does each fire relative to @BeforeEach?

level: middleimportance: should knowfreq 35%

basics

~20 s

prepareTestInstance fires once right after the test object is created (injection happens here). beforeTestMethod fires before each test, before @BeforeEach. beforeTestExecution fires after @BeforeEach, just before the test body runs. after* callbacks mirror them in reverse.

open as a page

How does constructor injection of beans into a JUnit 5 test class work with SpringExtension, and what are its constraints?

level: middleimportance: should knowfreq 45%

basics

~20 s

Because SpringExtension is also a JUnit 5 ParameterResolver, you can declare beans as constructor parameters of the test class and Spring injects them when it creates the test instance — no @Autowired fields needed. Add @Autowired on the constructor to disambiguate from other resolvers if required.

open as a page

What does @SpringJUnitConfig do, and how does it differ from @SpringJUnitWebConfig?

level: middleimportance: should knowfreq 55%

basics

~10 s

@SpringJUnitConfig is a composed annotation that combines @ExtendWith(SpringExtension.class) with @ContextConfiguration in one line. @SpringJUnitWebConfig does the same but also adds @WebAppConfiguration, giving you a WebApplicationContext for web-layer tests.

open as a page

How do @ActiveProfiles and @TestPropertySource work together, and when would you choose one over the other?

level: middleimportance: should knowfreq 40%

basics

~20 s

They solve different problems: @ActiveProfiles picks WHICH beans/config files are active; @TestPropertySource sets WHAT property values apply. Use profiles to swap wiring (mock vs real beans, a profile file); use @TestPropertySource to override individual property values. They combine freely.

open as a page

How does the TestContext cache bound its size, and what happens when the limit is exceeded?

level: seniorimportance: should knowfreq 35%

basics

~20 s

The cache holds a bounded number of contexts (default 32) and evicts the least-recently-used one when full. You change the limit with the spring.test.context.cache.maxSize property, set as a JVM system property or in a spring.properties file.

open as a page

What is ApplicationContextInitializer and how do you use the `initializers` attribute of @ContextConfiguration?

level: seniorimportance: should knowfreq 35%

basics

~20 s

An ApplicationContextInitializer is a callback that runs against the ConfigurableApplicationContext just before it's refreshed. You register it via @ContextConfiguration(initializers = ...) to programmatically tweak the context — add property sources, activate profiles, or register bean definitions.

open as a page

What is @ContextHierarchy and when would you use parent/child ApplicationContexts in tests?

level: seniorimportance: should knowfreq 30%

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

open as a page

How does Spring decide the order in which the default listeners (DependencyInjection, DirtiesContext, Transactional, Sql) run, and why does that order matter?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Each listener has an order value (via Ordered/@Order); Spring sorts by it. Lower value runs earlier in before* callbacks. Roughly: DependencyInjection (2000) < DirtiesContext (3000) < Transactional (4000) < Sql (5000). Order matters so injection and transactions are set up before SQL runs.

open as a page

How does method-level parameter injection work in Spring JUnit 5 tests, and what can you inject into @Test / @BeforeEach methods?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Via the same ParameterResolver, SpringExtension supplies parameters to test lifecycle methods. You can declare beans (optionally with @Autowired/@Qualifier/@Value) as parameters of @Test, @BeforeEach, @BeforeAll, etc., plus special types like ApplicationContext and WebApplicationContext, and Spring resolves them at invocation.

open as a page

Where does a @TestPropertySource source sit in the Environment's property-source ordering, and does it override application.properties and OS environment variables?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Spring inserts the @TestPropertySource properties as the highest-precedence source among the application's own property sources, so it overrides application.properties. It does not, however, override actual JVM system properties or OS environment variables, which Spring still ranks higher.

open as a page

You inherit a Spring Boot suite that takes 20 minutes because Spring reloads constantly. How do you diagnose and reduce the number of cached contexts?

level: principalimportance: should knowfreq 25%

basics

~20 s

Turn on cache logging to count distinct contexts, then eliminate needless key differences: standardize one base configuration, consolidate mocks, share a static Testcontainer, and remove stray @DirtiesContext and @TestPropertySource variations so tests reuse a few contexts.

open as a page

How does @ContextConfiguration inheritance/merging combine with the context cache key, and how do you control it?

level: principalimportance: should knowfreq 25%

basics

~20 s

All declared config (classes, locations, initializers, active profiles, property sources, loader, parent) is combined into a MergedContextConfiguration. Its equality is the cache key, so identical configs share one context. Superclass config is merged by default; inheritLocations/inheritInitializers=false to override.

open as a page

Design a custom TestExecutionListener that seeds and cleans up per-test data. Which callbacks do you implement, how do you register it without breaking defaults, and what are the pitfalls?

level: principalimportance: should knowfreq 20%

basics

~10 s

Implement TestExecutionListener, override beforeTestMethod to seed and afterTestMethod to clean up, give it @Order after the transactional listener, and register it via @TestExecutionListeners(listeners=..., mergeMode=MERGE_WITH_DEFAULTS) so defaults keep working.

open as a page

How do @ActiveProfiles and @TestPropertySource affect the TestContext context cache, and why can careless use slow a large test suite?

level: principalimportance: should knowfreq 30%

basics

~20 s

The set of active profiles and the test property sources are both part of the key the TestContext Framework uses to cache ApplicationContexts. Any difference produces a distinct key, so a new context is built instead of reusing a cached one — many unique combinations mean many expensive context startups.

open as a page

Under the hood, which JUnit 5 callback interfaces does SpringExtension implement, and how does that plug into TestContextManager and context caching?

level: principalimportance: nice to knowfreq 25%

basics

~10 s

SpringExtension implements several JUnit 5 callbacks — BeforeAll/AfterAll, BeforeEach/AfterEach, TestInstancePostProcessor, and ParameterResolver (plus TestExecutionExceptionHandler). Each callback delegates to a TestContextManager, which drives TestExecutionListeners and manages the cached ApplicationContext keyed by merged configuration.

open as a page