skip to content

Design a custom test slice for your own infrastructure module, and explain how slice design interacts with the TestContext cache and suite performance.

level: principalimportance: nice to knowfreq 22%

answer

  1. compose: @BootstrapWith + @OverrideAutoConfiguration(false) + @TypeExcludeFilters + @ImportAutoConfiguration
  2. cache key = full merged config
  3. @MockBean/@TestPropertySource variety fragments cache
  4. uniform slice → one reused context
  5. default cache 32, LRU; @DirtiesContext busts it

basics

~10 s

Create a composed annotation mirroring Boot's: @BootstrapWith a bootstrapper (or reuse SpringBootTest's), @OverrideAutoConfiguration(enabled=false), a @TypeExcludeFilter for your beans, and @ImportAutoConfiguration listing your auto-config. Keep configurations uniform so the TestContext cache reuses contexts across tests.

solid answer

~40 s

To build a custom slice, compose a meta-annotation the way Boot does: `@ExtendWith(SpringExtension.class)`, `@BootstrapWith(...)` (often `SpringBootTestContextBootstrapper` or a custom one), `@OverrideAutoConfiguration(enabled=false)` to default auto-config off, a `@TypeExcludeFilters` subclass to restrict component scanning to your module's stereotypes, and `@ImportAutoConfiguration` (or a dedicated `@AutoConfigureXxx` backed by an `.imports` manifest) enabling exactly your infrastructure's auto-config. The performance angle is the real principal-level point: Spring's `TestContext` framework caches `ApplicationContext`s keyed by their full configuration (annotations, active profiles, `@MockBean`/`@TestPropertySource`, etc.). Every distinct slice configuration is a distinct cache key and thus a fresh context boot. To keep a large suite fast, standardize slice usage, avoid gratuitous per-test `@MockBean`/property variations that fragment the cache, and prefer a few shared configurations. A custom slice that's used consistently boots once and is reused across all its tests.

code

java · 21 lines
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)          // 1. blanket auto-config OFF
@TypeExcludeFilters(CryptoTypeExcludeFilter.class)   // 2. scan only crypto stereotypes
@ImportAutoConfiguration                             // 3. enable curated auto-config
public @interface CryptoTest {

    @AliasFor(annotation = ImportAutoConfiguration.class, attribute = "classes")
    Class<?>[] value() default {};
}

// Usage — every test using @CryptoTest with the same shape shares ONE cached context:
@CryptoTest
class KeyRotationServiceTest {
    @Autowired KeyVault vault;
    // Avoid per-test @MockitoBean churn here — it would fork the context cache key.
}

go deeper

for a junior

Aware that a slice is a reusable annotation and that contexts are cached/reused.

for a middle

Can list the composing annotations of a slice and knows @MockBean triggers a new context.

for a senior

Explains the cache key composition and how mock/property variety fragments it; standardizes slice usage.

for a principal

Designs custom slices, reasons about suite wall-clock as distinct-contexts × boot-time, sets fork/cache/dirties policy across the org.

## Part 1 — Building a custom slice A slice is just a **composed annotation**; you can build your own for, say, a messaging or crypto infrastructure module. Mirror Boot's ingredients: ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @BootstrapWith(SpringBootTestContextBootstrapper.class) @ExtendWith(SpringExtension.class) @OverrideAutoConfiguration(enabled = false) // (1) default auto-config OFF @TypeExcludeFilters(CryptoTypeExcludeFilter.class) // (2) restrict bean scan @ImportAutoConfiguration // (3) enable curated auto-config public @interface CryptoTest { @AliasFor(annotation = ImportAutoConfiguration.class, attribute = "classes") Class<?>[] value() default {}; } ``` Ingredients explained: 1. **`@OverrideAutoConfiguration(enabled=false)`** — turns off Boot's blanket `@EnableAutoConfiguration`, so nothing auto-configures unless you opt it in. 2. **`@TypeExcludeFilters(CryptoTypeExcludeFilter.class)`** — your `TypeExcludeFilter` subclass narrows component scanning to just the module's stereotypes (e.g. `@CryptoComponent`), excluding services/controllers. 3. **`@ImportAutoConfiguration`** — value-less, it reads a `META-INF/spring/…CryptoTest.imports` manifest naming your `@AutoConfiguration` classes; or expose a `value()` aliased to `classes` so callers name them explicitly. You'd also ship a `META-INF/spring/<CryptoTest FQN>.imports` file listing the auto-config classes to enable. Optionally provide `@AutoConfigureCrypto` as a reusable enabler. ## Part 2 — The TestContext cache (why slice design is a perf decision) Spring's **TestContext framework caches `ApplicationContext` instances** across test classes/methods within a JVM. The cache **key** is the *merged context configuration*: the set of config/component classes, `@ActiveProfiles`, `@TestPropertySource` values, `@ContextConfiguration` initializers, `@MockBean`/`@MockitoBean` definitions, `@DynamicPropertySource`, resource-override attributes, web-environment, etc. If two tests produce the **same** key, they **share one already-booted context** — huge speedup. Any difference → a new context is built and cached (default cache size is 32 contexts, LRU-evicted; eviction closes the context). ### Implications for slice design - **Uniformity wins.** A custom slice used identically across many test classes yields **one** cached context reused everywhere. That's the point of slices for suite speed. - **`@MockBean`/`@MockitoBean` fragment the cache.** Each unique set of mocks changes the key → a distinct context. Sprinkling different mocks per test multiplies contexts and boot cost. Prefer stable configurations; mock at a consistent layer. - **`@TestPropertySource`/`@DynamicPropertySource` variety** likewise fragments. Consolidate. - **Too many bespoke slice variants** (each test adding its own `@ImportAutoConfiguration`, profiles, properties) defeats caching. Standardize a small number of slice 'shapes'. - **Dirtying:** `@DirtiesContext` evicts and rebuilds — use sparingly; it's a cache-buster. ### Rule of thumb Suite wall-clock ≈ (number of *distinct* context configurations) × (avg boot time) + test execution. Slices reduce boot time per context (fewer beans) **and**, when used uniformly, reduce the number of distinct contexts. Both levers matter; a carelessly parameterized slice can erase the first gain by exploding the second. ## Gotchas - A custom bootstrapper is rarely needed — reuse `SpringBootTestContextBootstrapper` unless you need special behavior. - Forgetting `@OverrideAutoConfiguration(enabled=false)` means your 'slice' still pulls full auto-config — no longer a slice. - The `.imports`/`spring.factories` registration must match your Boot version or nothing loads. - `@MockBean` is deprecated in favor of `@MockitoBean` (Spring 6.2 / Boot 3.4) but both still fragment the cache identically. - Context cache is **per JVM/fork** — parallel forks each pay their own boot cost; tune build fork strategy accordingly.

  • Why can adding a per-test @MockitoBean slow a large suite even though each test is fast?
    The mock set is part of the TestContext cache key. A unique mock configuration produces a distinct, separately-booted ApplicationContext instead of reusing a cached one, so you pay extra context startups across the suite.
  • What single annotation, if forgotten on a custom slice, would make it behave like @SpringBootTest?
    @OverrideAutoConfiguration(enabled=false). Without it, Boot's blanket @EnableAutoConfiguration stays on and the annotation pulls the full auto-config set rather than a curated subset.
  • How does @DirtiesContext interact with the cache?
    It marks the context dirty so it's closed and evicted after the test, forcing a rebuild for the next user of that configuration. Overuse defeats caching and slows the suite.

saying these in an interview costs you the question

  • Thinking each test always gets a brand-new context regardless of configuration (missing the cache)
  • Building a custom slice without @OverrideAutoConfiguration(enabled=false)
  • Assuming @MockBean/@TestPropertySource have no performance cost
  • Believing the context cache is shared across parallel test forks

context