What is @TestConfiguration and how does it differ from a regular @Configuration in a Spring test?
answer
- supplements, not replaces primary config
- nested must be static
- top-level needs @Import
- meta-annotated @TestComponent, excluded from scan
- plain @Configuration nested = becomes primary, breaks context
basics
~20 s@TestConfiguration is a special @Configuration used only in tests. It adds or overrides beans on top of your app's real configuration, instead of replacing it. A plain @Configuration found via scanning would replace the primary config.
solid answer
~40 s@TestConfiguration is a specialization of @Configuration meant for test-only beans (fakes, stubs, extra fixtures). Its key trait: when declared as a nested static class inside a test, it is NOT picked up by @SpringBootTest's normal component scan, so it does not replace your primary configuration — it supplements it. You either declare it as a static nested class (auto-detected only when it sits inside the test class) or pull it in explicitly with @Import. This lets you add test doubles or override specific beans while the rest of the real application context loads normally. A regular @Configuration nested in a test, by contrast, would be treated as the primary config and would suppress Spring Boot's auto-configuration and normal bean discovery.
code
java · 21 lines@SpringBootTest
class OrderServiceTest {
@Autowired
OrderService orderService;
// Nested + static => auto-applied for THIS test only, on top of real config
@TestConfiguration
static class Fixtures {
@Bean
Clock clock() {
// test double: frozen clock instead of the real system clock
return Clock.fixed(Instant.parse("2026-01-01T00:00:00Z"), ZoneOffset.UTC);
}
}
@Test
void usesFrozenClock() {
assertThat(orderService.now()).isEqualTo(Instant.parse("2026-01-01T00:00:00Z"));
}
}go deeper
Know it's a test-only @Configuration that adds/overrides beans without replacing the real config, and that nested versions must be static.
Explain the auto-detect-when-nested vs. @Import-when-top-level rule and the @TestComponent exclusion mechanism.
Contrast with plain @Configuration becoming primary config; discuss bean overriding rules and when to reach for @MockBean/@TestBean instead.
Reason about context caching implications, fixture sharing strategy across a large suite, and when a shared @Import fixture module beats per-test nested configs.
## What it is `@TestConfiguration` is an annotation in `org.springframework.boot.test.context`. It is meta-annotated with Spring's `@TestComponent` (itself a `@Component`) and, functionally, behaves like a `@Configuration` class — meaning it can declare `@Bean` methods. The word 'Test' signals two things: 1. **It is intended for test-only beans** — fakes, stubs, in-memory doubles, extra fixtures — not for production wiring. 2. **It has special detection rules** so it *supplements* rather than *replaces* the application's primary configuration. ## The core difference from @Configuration When you run a `@SpringBootTest`, Spring Boot finds your **primary configuration** — normally your `@SpringBootApplication` class — and loads the full context (auto-configuration + component scan). - A **nested static `@Configuration`** inside your test class is treated as the test's *primary* configuration. When Spring detects it, it **stops** looking for your `@SpringBootApplication` and does **not** apply Boot's normal auto-configuration/scan — you'd lose your real context. That is almost never what you want. - A **nested static `@TestConfiguration`** is explicitly **excluded** from being treated as primary config (it is annotated as a `@TestComponent`, which the standard `ComponentScan` excludes via `TypeExcludeFilter`). So the real primary config still loads, and the `@TestConfiguration`'s beans are layered on **top**. ## Two ways it gets applied 1. **Nested static class inside the test** — automatically detected and applied *for that test class only* (because it lives inside it). It must be `static`. 2. **Top-level (separate) class** — NOT auto-detected. You must pull it in explicitly with `@Import(MyTestConfig.class)` on the test (or via `@ContextConfiguration`). This is how you share fixtures across many test classes. ## Adding vs. overriding beans - **Adding**: a `@Bean` of a type not already present just joins the context. - **Overriding**: to replace an existing bean (e.g., swap the real `PaymentGateway` for a fake), you define a `@Bean` of the same type/name. Since Spring Boot 2.1, **bean overriding is disabled by default**, so a duplicate name throws `BeanDefinitionOverrideException`. Enable it in tests with `spring.main.allow-bean-definition-overriding=true`, or prefer `@MockBean`/`@TestConfiguration` with a matching bean **name**. (Modern Spring Boot 3.4+ also offers `@TestBean` for a cleaner override path.) ## When to use - Supplying a **fake/stub** implementation of a collaborator (e.g., a fake clock, fake email sender). - Providing **extra fixture beans** (seed data helpers, test-only `RestClient`, embedded resources) shared across tests via `@Import`. - Overriding a single bean's configuration for a slice of tests without touching production config. ## Gotchas - **Must be `static`** when nested — a non-static nested class won't be detected. - It is **not** auto-scanned as a top-level class — you must `@Import` it. Beginners forget this and wonder why fixtures never load. - Don't confuse it with plain `@Configuration` in tests — the replace-vs-supplement behavior is the whole point. - Bean **overriding** may need `spring.main.allow-bean-definition-overriding=true`.
- Why must a nested @TestConfiguration be declared static?Spring instantiates configuration classes without an enclosing test instance. A non-static (inner) class needs an enclosing instance to construct, so Spring cannot create it and silently won't detect/apply it. Static makes it independently constructable.
- If you put a plain @Configuration as a nested static class in a @SpringBootTest, what happens?Spring treats it as the primary configuration and stops discovering your @SpringBootApplication, so auto-configuration and the normal component scan don't run — you get a stripped-down context with only what that @Configuration declares.
saying these in an interview costs you the question
- Thinking @TestConfiguration replaces the whole application configuration (it supplements it).
- Believing a top-level @TestConfiguration is auto-detected without @Import.
- Forgetting the nested class must be static.
- Assuming bean overriding just works by default (it's disabled since Boot 2.1).