How do @TestConfiguration and nested static @Configuration classes interact with @SpringBootTest's detected configuration?
answer
- Detection/classes pick the base; TestConfig adds on top
- Nested @Configuration must be static, auto-included
- @TestConfiguration = @TestComponent, skipped by prod scan
- Reuse @TestConfiguration via @Import
- Override needs allow-bean-definition-overriding or use @MockBean
basics
~20 sA 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 sDetection 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@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
Know a nested static @Configuration adds test beans.
Distinguish additive @TestConfiguration/nested config from the replacing classes attribute; know the @Import reuse pattern.
Choose between @TestConfiguration and @MockBean and handle bean-override semantics.
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