Overriding beans in Integration tests
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Integration tests often need the real Spring container but not every real dependency inside it. A payment client may need to become a stub, a clock may need to become deterministic, or an email sender may need to become a no-op bean.
Spring gives you several ways to replace beans in tests, but they are not interchangeable. The best choice depends on whether you want a mock, a test-only implementation, or a profile-specific graph.
The Simplest Option: @MockBean
If the goal is to replace one dependency with a Mockito mock inside a Spring Boot test, @MockBean is usually the most direct tool:
This replaces the existing bean in the application context and injects the mock wherever that type is used.
Use @TestConfiguration for Real Test Implementations
If a mock is too thin and you want a small in-memory or deterministic implementation, use @TestConfiguration:
Then import it into the test:
This approach is especially good when you want realistic behavior without hitting external infrastructure.
Why @Primary Often Matters
If the application already defines a bean of the same type, your test bean may need @Primary so Spring knows which one to inject by default. Without it, you may get a "multiple beans of type" error unless you also use qualifiers.
@Primary is a tie-breaker, not a complete override mechanism by itself. It is most useful when paired with @TestConfiguration.
Profiles Work for Larger Test Graphs
If your test environment differs in several coordinated ways, a Spring profile can be cleaner than many one-off overrides.
Activate it in the test:
Profiles are useful when many beans need to change together, but they can become harder to reason about if overused.
Choose the Smallest Override That Solves the Problem
A good rule of thumb is:
- use
@MockBeanfor one dependency you want to stub or verify - use
@TestConfigurationfor small but real test implementations - use profiles when the whole test environment changes as a unit
That keeps tests readable and prevents a heavy test-specific container setup from drifting too far from production.
Keep Context Reuse in Mind
Spring test startup can be expensive. If every test class overrides beans differently, context caching becomes less effective and the suite slows down.
Try to group tests that share the same overridden context. This is not only faster; it also makes test wiring more consistent.
Common Pitfalls
The biggest mistake is overriding too much. If the test replaces half the application graph, it stops being a meaningful integration test.
Another common problem is forgetting @Primary or qualifiers when multiple beans of the same type exist. That leads to ambiguous injection errors that look unrelated to the test itself.
Teams also use profiles for tiny one-off overrides that would be clearer with @MockBean or @TestConfiguration. Profiles are powerful, but they add global state to the test context.
Finally, do not mock the class under test. Override its collaborators, then let the real target bean run inside the container.
Summary
- '
@MockBeanis the quickest way to replace one bean with a Mockito mock in a Boot integration test.' - '
@TestConfigurationis better when you want a small real test implementation.' - '
@Primaryhelps Spring choose the test bean when multiple candidates exist.' - Profiles are useful for larger test-specific wiring changes.
- Override the smallest possible part of the context to keep integration tests credible and fast.

