Testing with Spring
@SpringBootTest and @MockBean, with a complete example testing a service and mocking one of its dependencies.
Why testing a Spring app is different from a plain unit test
A plain JUnit test that does new OrderService(new FakePaymentGateway()) needs no Spring involvement at all — and for testing a single class's logic in isolation, that's still the right, fastest option. Spring's testing support exists for the layer above that: verifying that beans are wired correctly, that configuration loads as expected, and that a service behaves correctly as a Spring-managed bean with its real dependency graph (or a deliberately swapped-out piece of it).
@SpringBootTest: loading a real ApplicationContext
@SpringBootTest starts an actual Spring ApplicationContext for the test — the same container machinery that boots the real application, with every @Component/@Service/@Repository wired exactly as it would be at runtime:
@SpringBootTest
class OrderServiceIntegrationTest {
@Autowired
private OrderService orderService;
@Test
void placingAnOrderPersistsIt() {
Order order = orderService.placeOrder(new PlaceOrderRequest("SKU-1", 2));
assertThat(order.getId()).isNotNull();
}
}
This is thorough but expensive — every bean in the application gets constructed, which is slow compared to a plain unit test and pulls in real infrastructure (a real, or embedded, database; real configuration) unless something is deliberately swapped out.
Replacing one dependency with a mock: @MockBean
Often a test wants the real OrderService bean, wired by the real container, but with one of its dependencies swapped for a Mockito mock — for example, a PaymentGateway that would otherwise make a real network call:
public interface PaymentGateway {
PaymentResult charge(String cardToken, BigDecimal amount);
}
@Service
public class OrderService {
private final PaymentGateway paymentGateway;
private final OrderRepository orderRepository;
public OrderService(PaymentGateway paymentGateway, OrderRepository orderRepository) {
this.paymentGateway = paymentGateway;
this.orderRepository = orderRepository;
}
public Order placeOrder(PlaceOrderRequest request) {
PaymentResult result = paymentGateway.charge(request.cardToken(), request.amount());
if (!result.successful()) {
throw new PaymentFailedException(result.reason());
}
return orderRepository.save(new Order(request.sku(), request.quantity(), result.transactionId()));
}
}
@SpringBootTest
class OrderServiceTest {
@Autowired
private OrderService orderService;
@MockBean
private PaymentGateway paymentGateway; // replaces the real bean in the context with a Mockito mock
@Test
void successfulPaymentSavesTheOrder() {
when(paymentGateway.charge(anyString(), any(BigDecimal.class)))
.thenReturn(new PaymentResult(true, "txn-123", null));
Order order = orderService.placeOrder(new PlaceOrderRequest("SKU-1", 2, "tok-1", BigDecimal.TEN));
assertThat(order.getTransactionId()).isEqualTo("txn-123");
}
@Test
void failedPaymentThrowsAndNeverPersists() {
when(paymentGateway.charge(anyString(), any(BigDecimal.class)))
.thenReturn(new PaymentResult(false, null, "card_declined"));
assertThrows(PaymentFailedException.class, () ->
orderService.placeOrder(new PlaceOrderRequest("SKU-1", 2, "tok-1", BigDecimal.TEN))
);
}
}
@MockBean does two things: it registers a Mockito mock as a bean in the test's ApplicationContext, and it replaces whatever real bean of that type would otherwise have been wired — everywhere the container injects PaymentGateway, it now injects this mock instead, including into the real OrderService bean under test.
A note on @MockitoBean
Starting with Spring Boot 3.4, @MockBean is deprecated in favor of @MockitoBean (org.springframework.test.bean.override.mockito.MockitoBean), part of a more general "bean override" mechanism for tests. It's used exactly the same way (@MockitoBean private PaymentGateway paymentGateway;) — the rename exists because the new mechanism generalizes beyond just Mockito mocks. Existing @MockBean code still compiles and works on 3.4+, but new code should prefer @MockitoBean.
Where this fits with slice tests
@SpringBootTest loads the entire application context, which is correct for a genuine end-to-end test but overkill for testing just a web layer or just a repository. Spring Boot's narrower "slice" test annotations (@WebMvcTest, @DataJpaTest) load only the beans relevant to one layer, dramatically reducing startup cost — covered in depth on the Spring Boot track's own testing page, since they're Boot-specific rather than core-Spring features.
Common mistakes
- Reaching for
@SpringBootTestfor every test by default — it's the slowest and heaviest option; a plain unit test with hand-constructed dependencies (no Spring at all) is almost always faster and just as effective for testing one class's logic in isolation. - Forgetting that
@MockBeanreplaces the bean for every test in the class (and resets it between tests) — stubbing behavior in one@Testmethod that another test in the same class doesn't expect can cause confusing cross-test interference if mocks aren't re-stubbed per test. - Mocking a dependency that the test doesn't actually need to control, when the real bean (an in-memory implementation, or a genuinely fast dependency) would exercise more real behavior for the same effort.
- Using
@Autowiredfield injection heavily in test classes without a second thought — acceptable in tests (there's no constructor a test framework calls for you), unlike production code where constructor injection is strongly preferred.