skip to content

How does @MockitoBean affect the Spring TestContext context cache, and why can overusing it slow a test suite?

level: seniorimportance: should knowfreq 25%

answer

  1. cache key = MergedContextConfiguration
  2. BeanOverrideContextCustomizer in equals/hashCode
  3. different mock set → different context
  4. default ContextCache max = 32 → eviction thrash
  5. standardise mocks / push to slices/unit tests

basics

~20 s

The set of bean overrides is part of the context's cache key (MergedContextConfiguration). Two test classes with different @MockitoBean declarations get separate cached ApplicationContexts, so heavy or varied use causes many contexts to load — slower startup and more memory.

solid answer

~50 s

Spring caches ApplicationContexts keyed by MergedContextConfiguration. Declaring @MockitoBean adds a BeanOverrideContextCustomizer whose equals/hashCode reflect the exact set of overridden beans, and that customizer is part of the cache key. So a test class that mocks PaymentGateway will not share a cached context with one that mocks EmailSender, nor with a class that mocks nothing — each distinct override set forces a fresh context load. In a large suite this causes context proliferation: instead of a handful of reused contexts you load dozens, each costing JVM startup time and memory, and the default cache holds only 32 before eviction thrashing begins. Mitigations: standardise the mock set across related test classes so they share a key, push mocking down into faster slice or pure-unit tests, and avoid sprinkling one-off @MockitoBean fields that fragment the cache. It is a real, measurable driver of slow Spring suites.

code

java · 19 lines
java
// These two classes CANNOT share a cached context: different override sets.
@SpringBootTest
class CheckoutTest {
    @MockitoBean PaymentGateway paymentGateway; // key includes PaymentGateway override
}

@SpringBootTest
class SignupTest {
    @MockitoBean EmailSender emailSender;        // different key -> second context
}

// Sharing: a common base fixes the override set so subclasses reuse ONE context.
@SpringBootTest
abstract class MockedCollaboratorsTest {
    @MockitoBean protected PaymentGateway paymentGateway;
    @MockitoBean protected EmailSender emailSender;
}
class CheckoutTest2 extends MockedCollaboratorsTest { /* same key */ }
class SignupTest2  extends MockedCollaboratorsTest { /* same key -> shared context */ }

go deeper

for a junior

Just be aware mocking makes the test load a context and can be slower than a unit test.

for a middle

Know that different mock sets can lead to different contexts being loaded.

for a senior

Explain the MergedContextConfiguration cache key and BeanOverrideContextCustomizer precisely; give mitigations.

for a principal

Design a suite-wide strategy (shared base classes, slice/unit split, cache-size tuning) to bound context count.

## Background: the context cache Loading a Spring `ApplicationContext` is expensive, so the **TestContext Framework caches contexts** and reuses them across test classes/methods. The cache key is the **`MergedContextConfiguration`** (MCC): the merged set of config classes/locations, active profiles, `ApplicationContextInitializer`s, property sources, **and the set of `ContextCustomizer`s**. Two tests with an *equal* MCC share the same cached context; a different MCC means a **separate** context is built and cached. ## Where @MockitoBean enters the key Bean overrides are contributed through a **`ContextCustomizer`** (a `BeanOverrideContextCustomizer`). Its `equals`/`hashCode` are derived from the **complete set of bean-override definitions** in the test class (which beans, by which type/name, what kind of override). Because customizers participate in the MCC, the *presence and identity of your @MockitoBean set becomes part of the cache key*. Consequences: - A class with `@MockitoBean PaymentGateway` and a class with `@MockitoBean EmailSender` have **different** keys → **two** contexts. - A class with a mock and an otherwise-identical class **without** the mock have different keys → they **cannot** share a context. - Two classes that declare the **same** set of overrides *can* share one context. ## Why this slows suites: context proliferation Every distinct override combination is a fresh context load (component scanning, bean instantiation, auto-config). In a big codebase where many test classes each add a slightly different `@MockitoBean`, you can go from a few reused contexts to **dozens**. Symptoms: - Long total suite time dominated by repeated context startup. - High memory — each cached context holds its full bean graph. - The default `ContextCache` max size is **32**; exceed it and Spring **evicts** (LRU) and later **rebuilds** contexts, so you thrash and reload the ones you evicted — the worst case. You can observe this by enabling context-cache statistics logging (`org.springframework.test.context.cache` at DEBUG) — it logs hit/miss/size. ## Mitigations (what a senior should say) 1. **Standardise the mock set.** Group tests that need the same mocks so they share one MCC and one context (e.g., a shared abstract base test class declaring the overrides). 2. **Prefer narrower tests.** Move collaborator faking into **slice tests** (`@WebMvcTest`, `@DataJpaTest`) that load less, or better, **pure unit tests** with plain `@Mock` and no context at all. 3. **Avoid one-off overrides.** Each unique `@MockitoBean` field on a single class fragments the cache; consolidate. 4. **Don't mix override strategies pointlessly** — same beans mocked the same way = shared key. 5. **@DirtiesContext is worse.** It *evicts* the context after the test, forcing a rebuild for the next user — combine it with mocks only when truly necessary. ## Nuance The framework resets mocks between methods rather than rebuilding the context, so *within* a shared context you still get isolation without paying reload cost. The proliferation cost is **across** distinct override sets, not within one.

  • Two @SpringBootTest classes have identical config, but only one adds a @MockitoBean. Do they share a cached context?
    No. The bean-override customizer is part of the MergedContextConfiguration cache key, so the presence of the @MockitoBean makes the keys differ and Spring builds two separate contexts.
  • How would you diagnose context proliferation in a slow suite?
    Enable DEBUG logging for org.springframework.test.context.cache to see cache size, hits, and misses, and watch for the ContextCache exceeding its default max size of 32 with frequent misses/evictions.

saying these in an interview costs you the question

  • Claiming @MockitoBean has no effect on the context cache / that all @SpringBootTest classes share one context.
  • Thinking each test method reloads the context (it is cached; mocks are reset, not rebuilt).
  • Believing there is no upper bound on cached contexts (default max is 32, then LRU eviction).

context