Use @MockitoBean when a Spring test needs a Mockito mock in its application context. Spring replaces a matching bean with the mock, or creates one when the test slice did not load that bean. The controller receives the mock through normal dependency injection, so an HTTP test can control its collaborator without calling a real external service.
Spring Boot 4 removed its older @MockBean support. The current annotation is org.springframework.test.context.bean.override.mockito.MockitoBean from Spring Framework. This tutorial uses Spring Boot 4.1.2 to compare a plain Mockito unit test, an MVC slice test, and a full-context test.
1. Choose Between Mockito.mock() and @MockitoBean
A plain Mockito mock is an object created by the test. It is enough when the test constructs the class under test itself. It does not replace a bean in a Spring ApplicationContext. @MockitoBean is for tests that ask Spring to create the controller or service and inject its collaborators.
| Test setup | Mock mechanism | What gets tested |
|---|---|---|
| Construct the controller with new | Mockito.mock() or @Mock | Controller method without Spring MVC |
| Load only MVC components with @WebMvcTest | @MockitoBean | HTTP mapping, serialization and controller behavior |
| Load the application with @SpringBootTest | @MockitoBean | Controller and application wiring with a selected bean replaced |
For the controller below, a direct unit test constructs ShippingController with a mock rate service. It calls quote() as a Java method, so it checks the returned record but does not test /shipping/{orderId} routing or JSON.
ShippingRateService rates = mock(ShippingRateService.class);
when(rates.quoteInCents("A-17")).thenReturn(299);
ShippingController controller = new ShippingController(rates);
assertThat(controller.quote("A-17"))
.isEqualTo(new ShippingController.Quote("A-17", 299));
The Spring Framework bean override reference describes the two context behaviors: REPLACE_OR_CREATE by default and REPLACE when enforceOverride=true. The next tests show when each matters.
2. Define the Shipping Quote Feature
The example application returns a shipping quote for an order. ShippingRateService normally returns 499 cents. ShippingController receives the service in its constructor and returns a Quote record, which Spring MVC serializes as JSON. The tests supply different mock prices so they can prove which service the controller called.
@Service
public class ShippingRateService {
public int quoteInCents(String orderId) {
return 499;
}
}
@RestController
public class ShippingController {
private final ShippingRateService rates;
public ShippingController(ShippingRateService rates) {
this.rates = rates;
}
@GetMapping("/shipping/{orderId}")
public Quote quote(@PathVariable String orderId) {
return new Quote(orderId, rates.quoteInCents(orderId));
}
public record Quote(String orderId, int cents) {}
}
An ordinary request to /shipping/A-17 would return 499 cents. In the tests, the controller’s constructor still uses the same ShippingRateService type, but Spring supplies the mock in place of the normal bean.
3. Mock a Collaborator in an MVC Slice
@WebMvcTest(ShippingController.class) loads MVC infrastructure and the selected controller. It does not load the application’s service layer. Without a ShippingRateService bean, the controller cannot be constructed. Adding @MockitoBean on a test field creates a mock bean for that missing collaborator and injects it into the controller.
The Boot 4 project uses spring-boot-starter-webmvc-test for the MVC testing tools. The application itself uses spring-boot-starter-webmvc.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
The test stubs 249 cents for order A-17. MockMvc sends a request through the MVC stack without starting a real server. The JSON assertion verifies the mapping and response, while verify() checks that the controller called the mocked service with the expected order ID.
@WebMvcTest(ShippingController.class)
class ShippingControllerSliceTest {
@Autowired MockMvc mvc;
@MockitoBean ShippingRateService rates;
@Test
void returnsStubbedQuote() throws Exception {
when(rates.quoteInCents("A-17")).thenReturn(249);
mvc.perform(get("/shipping/A-17"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.orderId").value("A-17"))
.andExpect(jsonPath("$.cents").value(249));
verify(rates).quoteInCents("A-17");
}
}
The result is 249 rather than the real service’s 499. This proves the controller in this slice received the mock. It does not test the real shipping calculation, because the stub supplies the price. For a broader test strategy, see Spring Boot integration testing.
4. Replace a Bean in the Full Application Context
@SpringBootTest loads the application, including the real ShippingRateService definition. Here @MockitoBean(enforceOverride = true) requires that a matching bean already exists. The test fails during context setup if the service is absent, which helps catch a mistaken type or component scan change instead of silently creating a new mock.
Boot 4 does not automatically configure MockMvc for @SpringBootTest, so the test also uses @AutoConfigureMockMvc. The Spring Boot 4 migration guide calls out both changes.
@SpringBootTest
@AutoConfigureMockMvc
class ShippingControllerFullContextTest {
@Autowired MockMvc mvc;
@MockitoBean(name = "shippingRateService", enforceOverride = true)
ShippingRateService rates;
@Test
void replacesExistingService() throws Exception {
when(rates.quoteInCents("A-17")).thenReturn(199);
mvc.perform(get("/shipping/A-17"))
.andExpect(jsonPath("$.cents").value(199));
}
}
The response is 199 cents, which differs from both the normal 499 and the slice test’s 249. The test establishes that the full-context controller uses the replacement bean. It still does not exercise the real rate calculation; that behavior needs its own test.
5. Migrate from @MockBean Carefully
Spring Boot’s @MockBean and @SpyBean were removed in Boot 4 in favor of Spring Framework’s @MockitoBean and @MockitoSpyBean. For a mock field in a test class, changing the import and annotation is usually straightforward. Check whether the old test relied on @MockBean in a @Configuration class or on older MockMvc auto-configuration, because those cases need a different arrangement.
For shared mock definitions, Spring Framework also supports a type-level annotation. This second slice test supplies a ShippingRateService mock at the class level. The test can then inject it to stub a price before making the HTTP request.
@WebMvcTest(ShippingController.class)
@MockitoBean(types = ShippingRateService.class)
class ShippingControllerTypeLevelTest {
@Autowired MockMvc mvc;
@Autowired ShippingRateService rates;
@Test
void typeLevelMockSuppliesTheController() throws Exception {
when(rates.quoteInCents("A-17")).thenReturn(149);
mvc.perform(get("/shipping/A-17"))
.andExpect(jsonPath("$.cents").value(149));
}
}
The response is 149 cents, so the type-level mock reaches the controller. For several mocks, repeat @MockitoBean with explicit types on the test class. The earlier Spring Framework reference explains the available forms and how qualifiers identify a bean.
6. Frequently Asked Questions
6.1. Why Does @MockitoBean Create a Bean Instead of Replacing One?
The default strategy is REPLACE_OR_CREATE. An MVC slice deliberately leaves out service components, so its @MockitoBean creates the collaborator needed by the controller. If the test is meant to replace an existing bean, use enforceOverride=true as in section 4; setup then fails when no matching bean exists.
6.2. What If Two Beans Have the Same Type?
Spring needs a way to identify the target. Add a bean name to @MockitoBean or use @Qualifier on the field. Without an explicit qualifier, the field name is a fallback qualifier. Keep that name stable across tests that should share a cached application context.
@MockitoBean(name = "shippingRateService", enforceOverride = true)
ShippingRateService rates;
The full-context test uses the actual bean name shippingRateService. It has only one bean of this type, so the name is optional there; the explicit name illustrates how to select a bean when several candidates exist.
6.3. Can a Mock Prove the Real Service Works?
No. The stubbed values in these tests prove controller behavior and wiring for the simulated responses. Test the real ShippingRateService separately with representative inputs and expected prices. Use a full integration test when database or network behavior matters.
7. Conclusion
Use a plain Mockito mock for a class constructed directly in a unit test. Use @MockitoBean when Spring creates the class under test and must inject a mock collaborator. In a slice, the annotation can create a missing bean; in a full-context test, enforceOverride=true makes the replacement requirement explicit.
Happy Learning!