When would you supply a fake as a @Bean in a @TestConfiguration versus using @MockBean (or @TestBean)? What are the trade-offs?
answer
- fake = real behavior, shared, cache-friendly
- @MockBean = Mockito mock, per-test, resets
- @MockBean alters context => cache-key fragmentation
- @TestBean = Boot 3.4+ successor
- state-based vs interaction-based testing
basics
~20 sUse 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.
solid answer
~40 sA `@Bean` fake in `@TestConfiguration` is a real implementation (e.g. an in-memory repository, a recording email sender) that lives in the context and behaves deterministically — great when many tests need the same believable collaborator and you don't want per-test stubbing. `@MockBean` replaces a bean with a **Mockito mock** you configure with `when(...).thenReturn(...)` and verify with `verify(...)`; it's per-test and interaction-focused, but it **resets between test methods** and, crucially, **changes the context cache key**, so heavy @MockBean use can fragment the cache and slow the suite. `@TestBean` (Boot 3.4+) is the newer, cache-friendlier field-based override. Rule of thumb: prefer fakes for shared, stateful, behavior-driven fixtures; reach for @MockBean/@TestBean when a single test needs to script or verify a collaborator's interactions.
code
java · 29 lines// Fake @Bean: deterministic, reused, cache-friendly
@TestConfiguration
class RepoFixtures {
@Bean
CustomerRepository customerRepository() {
return new InMemoryCustomerRepository(); // real, working, in-memory
}
}
@SpringBootTest
@Import(RepoFixtures.class)
class CustomerServiceTest {
@Autowired CustomerService service;
// ...state-based assertions against real fake behavior
}
// Mock: per-test scripting + verification (changes the context cache key)
@SpringBootTest
class NotificationServiceTest {
@MockBean PaymentGateway gateway; // Mockito mock replaces the bean
@Autowired NotificationService service;
@Test
void notifiesOnDecline() {
when(gateway.charge(any())).thenReturn(ChargeResult.DECLINED);
service.process(order());
verify(gateway).charge(any()); // interaction-based assertion
}
}go deeper
Know a fake is a real hand-written double and @MockBean is a Mockito mock you stub/verify.
Explain reset semantics and the add-vs-replace distinction, and when each style fits.
Reason about context-cache fragmentation from @MockBean, state-leak in shared fakes, and state-based vs interaction-based trade-offs.
Set suite-wide policy: favor shared fakes to preserve cache hits, confine @MockBean/@TestBean to slice tests, and quantify suite runtime impact.
## Two philosophies of test double - **Fake as `@Bean`** (in a `@TestConfiguration`): a *real*, hand-written implementation with working logic — e.g. an in-memory `Map`-backed repository, a `RecordingEmailSender`, a `Clock.fixed(...)`. It behaves consistently for every test that uses it. - **Mock via `@MockBean`**: `@MockBean` (from `org.springframework.boot.test.mock.mockito`) removes any existing bean of that type from the context and inserts a **Mockito mock** in its place. You then stub (`when(...).thenReturn(...)`) and verify (`verify(...)`) per test. Mockito resets the mock **after each test method**. ## Key mechanical differences ### Context cache - A `@TestConfiguration` fixture that's imported identically across tests keeps a **single shared, cached context**. Fast. - `@MockBean` (and `@SpyBean`) **modify the context** and therefore become part of the **context cache key**. Two tests differing only in their `@MockBean` sets get **different** cached contexts. Sprinkling `@MockBean` broadly fragments the cache and increases build time + memory. This is one of the top causes of slow Spring test suites. - `@TestBean` (Spring Framework 6.2 / Boot 3.4+) is the modern replacement: a field annotated `@TestBean` supplied by a static factory method. It's designed as the successor to `@MockBean`/`@SpyBean` (which are deprecated for removal in Boot 3.4+), but it still alters the context and thus the cache key — the win is a cleaner model, not free caching. ### Behavior vs. interaction - **Fakes** give you *state-based* testing: exercise real behavior, assert on resulting state. Reusable, no per-test wiring. - **Mocks** give you *interaction-based* testing: script return values, verify calls/arguments/ordering. Best when the collaborator is expensive, non-deterministic, or when the *interaction itself* is what you're asserting. ### Reset semantics - Mockito mocks are reset between methods automatically (`MockitoTestExecutionListener`), so stubs don't leak. - A fake carrying mutable state is **NOT** reset automatically; if the context is reused, state leaks across tests. Reset it in `@BeforeEach`, or (last resort) use `@DirtiesContext`. ## Overriding rules - Adding a fake `@Bean` of the **same type/name** as a production bean is a bean override → needs `spring.main.allow-bean-definition-overriding=true` (off by default since Boot 2.1). `@MockBean`/`@TestBean` are purpose-built to *replace* an existing bean without that flag. ## Decision guide | Situation | Prefer | |---|---| | Same believable collaborator across many tests | Fake `@Bean` in `@TestConfiguration` | | Need real, deterministic logic (in-memory repo, fixed clock) | Fake `@Bean` | | Single test must script a specific return | `@MockBean` / `@TestBean` | | Need to `verify(...)` interactions/arguments | `@MockBean` | | Minimizing context-cache fragmentation across the suite | Fake `@Bean` (shared) | | Slice tests (`@WebMvcTest`) needing service stubs | `@MockBean` (idiomatic there) | ## Gotchas - Over-mocking couples tests to implementation call patterns (brittle). - Broad `@MockBean` usage silently balloons suite time via cache misses. - Fakes must be reset if stateful and context is shared. - Mixing both: a `@TestConfiguration` fake plus a `@MockBean` of the same type — the `@MockBean` wins (it replaces the bean).
- Why can heavy @MockBean usage slow a large test suite?@MockBean modifies the ApplicationContext, so it's part of the context cache key. Tests differing only in their mocks can't share a cached context, causing more context builds and higher memory — a common suite-slowdown cause.
- You have a fake CustomerRepository @Bean AND a @MockBean CustomerRepository in the same test. Which wins?The @MockBean wins — it replaces the existing bean of that type in the context, so the Mockito mock is injected, not the fake.
- What replaces @MockBean/@SpyBean in recent Spring Boot?@TestBean (and its spy equivalent) from Spring Framework 6.2 / Boot 3.4+. @MockBean and @SpyBean are deprecated for removal; @TestBean uses a field + static factory method to supply the override.
saying these in an interview costs you the question
- Claiming @MockBean doesn't affect the context cache.
- Believing a stateful fake resets automatically between tests.
- Thinking you always need @DirtiesContext after using @MockBean.
- Asserting you must enable bean overriding to use @MockBean (you don't — it's built to replace).