skip to content

@TestConfiguration & Fixtures

A nested @TestConfiguration adds or replaces beans for a test without displacing the application's own configuration, and can be imported where needed. This is the idiomatic home for shared test fixtures.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is @TestConfiguration and how does it differ from a regular @Configuration in a Spring test?

level: juniorimportance: must knowfreq 70%

answer

  1. supplements, not replaces primary config
  2. nested must be static
  3. top-level needs @Import
  4. meta-annotated @TestComponent, excluded from scan
  5. 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
java
@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

for a junior

Know it's a test-only @Configuration that adds/overrides beans without replacing the real config, and that nested versions must be static.

for a middle

Explain the auto-detect-when-nested vs. @Import-when-top-level rule and the @TestComponent exclusion mechanism.

for a senior

Contrast with plain @Configuration becoming primary config; discuss bean overriding rules and when to reach for @MockBean/@TestBean instead.

for a principal

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).

context

open as a page

How do you share test fixtures (test doubles, seed helpers) across multiple test classes using @TestConfiguration and @Import?

level: middleimportance: should knowfreq 55%

basics

~10 s

Put the fixture beans in a top-level class annotated @TestConfiguration, then add @Import(TestFixtures.class) to each test class that needs them. Because it's top-level, it isn't auto-scanned, so @Import is what wires it in.

open as a page

How do @TestConfiguration and test doubles behave inside slice tests like @WebMvcTest or @DataJpaTest, compared to a full @SpringBootTest?

level: middleimportance: should knowfreq 40%

basics

~20 s

Slice tests load only a narrow part of the context. A nested static @TestConfiguration in the test still applies, but auto-detection of other @TestConfiguration classes is limited, so you usually add fixtures with @Import. Collaborators outside the slice must be supplied as @MockBean or fake @Beans.

open as a page

When would you supply a fake as a @Bean in a @TestConfiguration versus using @MockBean (or @TestBean)? What are the trade-offs?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Use a hand-written fake @Bean in @TestConfiguration when you want real, deterministic behavior reused across tests. Use @MockBean/@TestBean when you want a per-test Mockito mock you stub and verify. Fakes favor stable behavior; mocks favor per-test control.

open as a page

Explain bean overriding and precedence when a @TestConfiguration defines a bean that already exists in the primary context. What errors can occur and how do you control which bean wins?

level: principalimportance: should knowfreq 35%

basics

~20 s

Since Spring Boot 2.1, defining a duplicate bean name throws BeanDefinitionOverrideException by default. To override in tests you either enable spring.main.allow-bean-definition-overriding=true, match by bean name so the test definition replaces the original, or use @MockBean/@TestBean which are built to replace beans safely.

open as a page