skip to content

How do @TestConfiguration and nested static @Configuration classes interact with @SpringBootTest's detected configuration?

level: middleimportance: nice to knowfreq 40%

answer

  1. Detection/classes pick the base; TestConfig adds on top
  2. Nested @Configuration must be static, auto-included
  3. @TestConfiguration = @TestComponent, skipped by prod scan
  4. Reuse @TestConfiguration via @Import
  5. Override needs allow-bean-definition-overriding or use @MockBean

basics

~20 s

A nested static @Configuration inside the test is picked up automatically and supplements the detected config. @TestConfiguration is additive too but only applies when imported or when it's a nested class of the test — it adds or overrides beans without replacing the discovered configuration.

solid answer

~40 s

Detection selects the *primary* configuration (your `@SpringBootConfiguration`); `@TestConfiguration` and nested `@Configuration` are *additive* on top of it, not replacements. A `static` nested `@Configuration` declared inside the test class is automatically detected as part of the context alongside the discovered primary config. `@TestConfiguration` is a specialization that is excluded from normal component scanning (so it never leaks into production scans) and is applied when it's a nested class of the running test or explicitly pulled in with `@Import`. Both are typically used to add test-only beans or override real ones (e.g. a stub clock, an in-memory client). Contrast with the `classes` attribute, which *replaces* discovery. So the mental model: `classes` = choose the base; `@TestConfiguration`/nested `@Configuration` = layer extras onto whatever base is in effect. Bean overriding may require `spring.main.allow-bean-definition-overriding=true` depending on the Boot version.

code

java · 22 lines
java
@SpringBootTest
class PaymentServiceTest {

    // Auto-detected, supplements the discovered primary config
    @TestConfiguration
    static class StubbedClients {
        @Bean Clock clock() { return Clock.fixed(Instant.EPOCH, ZoneOffset.UTC); }
    }

    @Autowired Clock clock;      // the fixed test clock
    @Autowired PaymentService service; // real bean from the full app context
}

// Reusable across many tests via import:
@TestConfiguration
public class FakeGatewayConfig {
    @Bean PaymentGateway gateway() { return new InMemoryGateway(); }
}

@SpringBootTest
@Import(FakeGatewayConfig.class)
class CheckoutTest { /* gateway bean replaced by the fake */ }

go deeper

for a junior

Know a nested static @Configuration adds test beans.

for a middle

Distinguish additive @TestConfiguration/nested config from the replacing classes attribute; know the @Import reuse pattern.

for a senior

Choose between @TestConfiguration and @MockBean and handle bean-override semantics.

for a principal

Standardize reusable @TestConfiguration modules and override policy to keep contexts cache-friendly and test-only config isolated.

### Additive vs replacing There are two distinct operations: - **Choosing the primary configuration** — done by detection or by the `classes` attribute. - **Supplementing it** — done by `@TestConfiguration` or a nested `@Configuration`, which *add to* whatever primary is in effect. ### Nested static @Configuration A `public static class` annotated `@Configuration` **inside** your test class is automatically included in the test's context — Spring's TestContext framework treats an inner `@Configuration` as part of the configuration for that test. It's combined with the detected primary config, so you get the full app **plus** these extra bean definitions. Must be `static`. ### @TestConfiguration `@TestConfiguration` (from `org.springframework.boot.test.context`) is a `@Configuration` specialization with two special properties: 1. It's annotated with `@TestComponent`, which is filtered out of normal `@ComponentScan`, so it will **not** be picked up by the production app scan — it stays test-only. 2. It's applied when it is a **nested class of the current test** (auto-detected) or when **explicitly `@Import`ed** into a test. This makes it reusable across tests via import, unlike a plain nested `@Configuration` which is tied to its enclosing test. ### Typical use Override or add beans for testing: a fixed `Clock`, a fake external gateway, a test data source. Example: a top-level `@TestConfiguration class StubClockConfig { @Bean Clock clock() { return Clock.fixed(...); } }` imported via `@Import(StubClockConfig.class)` into any `@SpringBootTest`. ### Interaction with `classes` `classes` replaces detection with an explicit primary. `@TestConfiguration`/nested `@Configuration` are orthogonal — they add on top of either the detected or the explicitly-named primary. You can combine them: `@SpringBootTest(classes = LeanConfig.class)` + `@Import(StubClockConfig.class)`. ### Overriding gotcha Defining a bean of a type/name that already exists overrides the original **only if bean-definition overriding is allowed**. Since Boot 2.1 this defaults to disabled; enable via `spring.main.allow-bean-definition-overriding=true` or, better, use `@MockBean`/`@MockitoBean` which is designed to replace an existing bean cleanly. Prefer `@MockBean` for swapping a single collaborator; `@TestConfiguration` for adding several coordinated test beans. ### Summary - `classes` → replaces primary config. - nested static `@Configuration` → adds beans, test-local. - `@TestConfiguration` (nested or imported) → adds beans, test-only, reusable via import. - `@MockBean`/`@MockitoBean` → replaces one bean with a mock.

  • Why is @TestConfiguration not picked up by your application's component scan?
    It's meta-annotated with @TestComponent, and Spring Boot's default component scan applies a TypeExcludeFilter that skips @TestComponent-annotated classes. This keeps test-only configuration out of the production context — it's only applied when nested in the running test or explicitly @Imported.
  • When would you prefer @MockBean over a @TestConfiguration bean to substitute a collaborator?
    Use @MockBean/@MockitoBean when you want to replace a single existing bean with a Mockito mock and stub its behavior per test — it handles replacement without bean-override flags. Use @TestConfiguration when you need several coordinated real (non-mock) test beans or a shared, importable setup.

saying these in an interview costs you the question

  • Thinking @TestConfiguration replaces the detected config like the classes attribute
  • Forgetting the nested @Configuration must be static
  • Assuming bean overriding is enabled by default (it's off since Boot 2.1)
  • Believing @TestConfiguration leaks into the production component scan

context