Mocking Service Layers
Every test makes a decision: which collaborators are real and which are fakes? Getting that boundary right is what makes a test fast, focused, and trustworthy. This page lays out the two main mocking mechanisms in Spring Boot — plain Mockito @Mock and Spring’s @MockitoBean — and when to use each.
Two mechanisms, two contexts
The key distinction is whether a Spring context is running.
- Plain Mockito
@Mockcreates a mock object in plain Java. No Spring context is involved. You inject it yourself (usually via@InjectMocks). This is for pure unit tests. @MockitoBeanregisters a Mockito mock inside the Spring context, replacing the real bean of that type. The rest of the context is wired normally. This is for slice and integration tests that boot a context.
Note:
@MockitoBean(and@MockitoSpyBean) arrived in Spring Boot 3.4 / Spring Framework 6.2 and replace the now-deprecated@MockBeanand@SpyBean. New code should use@MockitoBean.
@MockitoBean vs Mockito @Mock
@Mock (Mockito) | @MockitoBean (Spring) | |
|---|---|---|
| Spring context | None | Required (slice / @SpringBootTest) |
| What it does | Creates a bare mock object | Replaces a bean in the context with a mock |
| Injection | Manual / @InjectMocks | Spring autowires it everywhere the bean is used |
| Enabling | @ExtendWith(MockitoExtension.class) | Automatic in a Spring test |
| Speed | Fastest (no context) | Slower (context starts) |
| Use in | Pure unit tests | @WebMvcTest, @SpringBootTest slices |
@Mock in a unit test
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock OrderRepository repository;
@InjectMocks OrderService service; // mock injected by Mockito
@Test
void findsOrder() {
when(repository.findById(1L)).thenReturn(Optional.of(new Order(1L)));
assertThat(service.find(1L)).isNotNull();
}
}
@MockitoBean in a slice test
In a @WebMvcTest the service layer is not loaded, so you supply it as a context-level mock.
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired MockMvc mockMvc;
@MockitoBean OrderService service; // replaces the real bean in the context
@Test
void returnsOrder() throws Exception {
when(service.find(1L)).thenReturn(new Order(1L));
mockMvc.perform(get("/orders/1")).andExpect(status().isOk());
}
}
Stubbing repositories
When unit-testing a service, mock its repository and stub the methods the code path uses. Return realistic values — including the empty cases.
when(repository.findById(1L)).thenReturn(Optional.of(order));
when(repository.findById(404L)).thenReturn(Optional.empty()); // the not-found path
when(repository.save(any(Order.class))).thenAnswer(inv -> inv.getArgument(0));
Tip: Stubbing
save(...)to echo its argument (thenAnswer(inv -> inv.getArgument(0))) is handy when the code uses the saved entity afterward but you do not care about a generated ID.
Faking external clients
Calls to third-party APIs (RestClient, WebClient, an SDK) must never hit the network in a test. Hide each external call behind your own interface, then mock that interface — your tests stay fast and you do not depend on a remote service being up.
public interface PaymentGateway {
PaymentResult charge(String cardToken, BigDecimal amount);
}
@Mock PaymentGateway gateway;
when(gateway.charge(eq("tok_visa"), any()))
.thenReturn(new PaymentResult("CONFIRMED", "ch_123"));
// And assert your code reacts correctly to a declined charge:
when(gateway.charge(eq("tok_declined"), any()))
.thenReturn(new PaymentResult("DECLINED", null));
For testing the client itself, the @RestClientTest slice provides a MockRestServiceServer to stub HTTP responses without a real server.
Mocks vs stubs vs fakes
“Mock” is often used loosely. The distinctions guide what you assert.
| Double | What it is | You typically |
|---|---|---|
| Stub | Returns canned answers | Assert your code’s output |
| Mock | A stub that also records calls | verify(...) an interaction happened |
| Fake | A working lightweight implementation (in-memory repo, H2) | Treat it like the real thing |
| Spy | Wraps a real object, selectively overridden | Override one method, keep the rest |
Test doubles vs real beans — choosing the boundary
Mock at the edges of what you are testing, and use real beans inside. Over-mocking couples tests to implementation details and lets bugs slip between the seams; under-mocking makes tests slow and flaky.
- Unit test of a service → mock its repository and external clients; the service itself is real.
@WebMvcTest→ mock the service (@MockitoBean); the controller, validation, and JSON are real.@DataJpaTest→ mock nothing — use the real repository against a real (or embedded) database. See @DataJpaTest.- Full integration test → mock as little as possible, ideally only truly external systems; use Testcontainers for the database.
Warning: Resist mocking types you do not own (e.g. a third-party SDK class) directly. Wrap them behind your own interface and mock that. When the library changes, you update one adapter instead of dozens of brittle mock setups.