@Mock vs. @MockBean When Testing Spring Boot Applications
When you start testing your Spring Boot application sooner or later you’ll stumble over @Mock and @MockBean. Both annotations create mock objects but with a slightly different purpose. This might be confusing in the beginning. With this blog post, I’ll resolve this confusion and explain the difference between @Mock
and @MockBean
when it comes to testing Spring Boot applications.
tl;dr: Use @Mock
when unit testing your business logic (only using JUnit and Mockito). Use @MockBean
when you write a test that is backed by a Spring Test Context and you want to add or replace a bean with a mocked version of it.
Using @Mock For Spring Boot Unit Tests
Let’s use the following example: We have a StockService
that requires an instance of StockApiClient
(a so-called collaborator) to return the latest price of a stock. The actual implementation does not matter here.
@Service public class StockService { private final StockApiClient stockApiClient; private Set<String> techCompanies = Set.of("AAPL", "MSFT", "GOOG"); public StockService(StockApiClient stockApiClient) { this.stockApiClient = stockApiClient; } public BigDecimal getLatestPrice(String stockCode) { if (techCompanies.contains(stockCode)) { return BigDecimal.valueOf(Double.MAX_VALUE); } try { return stockApiClient.getLatestStockPrice(stockCode); } catch (Exception e) { e.printStackTrace(); return BigDecimal.ZERO; } } }
When testing this class in isolation we only need two tools: JUnit (4 or 5) and Mockito.
While writing unit tests for the StockService
, we mock its collaborators. We don’t want a change in their implementation to affect our unit test. In this example, we mock the StockApiClient
as our class under test (short cut
) only has one collaborator.
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class StockServiceTest { @Mock private StockApiClient stockApiClient; @InjectMocks private StockService cut; @Test void shouldReturnDefaultPriceWhenClientThrowsException() { when(stockApiClient.getLatestStockPrice("AMZN")) .thenThrow(new RuntimeException("Remote System Down!")); BigDecimal result = cut.getLatestPrice("AMZN"); assertEquals(BigDecimal.ZERO, result); } }
The test above uses JUnit Jupiter (a module of JUnit 5) and Mockito. With @Mock
we instruct Mockito to create a mock as we don’t want a real instance of this class. Mockito’s JUnit Jupiter extension will then take care to instantiate the mock and inject it to our class under test.
The word inject might be misleading if you think of Spring’s dependency injection when you read @InjectMocks
. This is a utility from Mockito, that takes the work of creating an instance of the class under test off our hands. In a nutshell, Mockito will search for a suitable public constructor to create an instance of our StockService
and pass all mocks (we only have one) to it.
While unit testing our business logic, in this example the StockService
, it does not matter which application framework we use. We can achieve this mocking behavior using @Mock
whether we use Spring Boot or any other framework like Jakarta EE, Quarkus, Micronaut, Helidon, etc.
Using @MockBean to Add or Replace Beans with a Mock
While the previous section was true for using plain JUnit 5 (or JUnit 4) with Mockito and independent of the application framework, what follows is only applicable for the Spring Framework.
With Spring Boot’s excellent test support, we can create a custom Spring Context for our test. Most of the time we either populate the full Spring Context (@SpringBootTest
) or use a sliced context (e.g. @WebMvcTest
or @DataJpaTest
). This allows us to test the integration of multiple classes or for example the web layer in isolation with a mocked Servlet environment.
Such tests now use a Spring Test Context. This context is similar to the application context during runtime as we can request beans from it (@Autowired
). Usually, it only contains a subset of our beans (making our tests faster).
When starting the Spring Test Context we have to satisfy all dependencies (speak collaborator) of our Spring beans. We do this by providing an instance of them inside the Spring Context so that Spring can inject them. Otherwise, the context won’t start.
As we can customize the Spring Test Context to our needs, we can decide whether we want the actual or a mocked version of one of our beans inside the context.
An example might illustrate this better. Let’s say we have the following Spring MVC controller:
@RestController @RequestMapping("/api/stocks") public class StockController { private final StockService stockService; public StockController(StockService stockService) { this.stockService = stockService; } @GetMapping public BigDecimal getStockPrice(@RequestParam("stockCode") String stockCode) { return stockService.getLatestPrice(stockCode); } }
With Spring Boot’s @WebMvcTest
we can now start a sliced Spring context that includes only beans that are relevant for our web-layer. As this Test Context also includes a bean of type StockController
, we have to provide a bean of type StockService
as otherwise, our context won’t start.
We could add the actual implementation of the StockService
, but this would mean that we also have to provide all collaborators of the StockService
. This might end up in a big hierarchy of dependencies that we would have to provide.
On the other side, we also want to test our web-layer in isolation and don’t care what’s going on inside the StockService
for such a test. That’s why we use @MockBean
here to place a mocked version of StockService
inside the context to satisfy our StockController
:
@WebMvcTest(StockController.class) class StockControllerTest { @MockBean private StockService stockService; @Autowired private MockMvc mockMvc; @Test void shouldReturnStockPriceFromService() throws Exception { when(stockService.getLatestPrice("AMZN")) .thenReturn(BigDecimal.TEN); this.mockMvc .perform(get("/api/stocks?stockCode=AMZN")) .andExpect(status().isOk()); } }
The @MockBean
annotation is part of Spring Test and will place a mock of type StockService
inside the Spring Test Context. We can then define the behavior of this mock using the well-known Mockito stubbing setup: when().thenReturn()
.
You can use this annotation whenever our test deals with a Spring Context.
Let’s say we want to write an integration test that involves all our beans. However, we have a ExpensiveRealtimeStockApiClient
that talks to a remote system and we are billed for every API call. The lazy developer’s approach could be to mock this bean for our integration test (PS: I know that WireMock would fit better here).
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class ApplicationTest { @Autowired private StockApiClient stockApiClient; @MockBean private ExpensiveRealtimeStockApiClient expensiveRealtimeStockApiClient; @Test void contextLoadsWithAllBeans() { // e.g. now use the WebTestClient to access our endpoint } }
A simplified visualization of the Spring Test Context when mocking a bean looks like this:
![Spring Context @MockBean example](https://cdn-aoloc.nitrocdn.com/LjZAnpMBiKRdWkhommfnyugsAOnHWdxL/assets/images/optimized/rev-742cb3d/rieckpil.de/wp-content/uploads/2020/11/spring-test-context-mockbean-example.png)
The green rectangles represent the actual (speaking real implementation) Spring bean, whereas an orange rectangle is a mock. On the left side, you see the Spring Context without any mocks. The right side however represents the Spring Context for our ApplicationTest
above where place a mocked version of the ExpensiveRealtimeStockApiClient
inside the context.
General Advice on Using @MockBean for Your Spring Boot Tests
As general advice, don’t overdo it with @MockBean
. I’ve seen tests that verify a small part of an application using @SpringBootTest
and mocking almost everything. Always starting the whole Spring Context to test isolated business logic is overkill. Always prefer to write a unit test that makes use of JUnit and Mockito, as they are fast.
However, there are parts of your application where plain unit tests won’t add many benefits. Your controller endpoints are a good example (see Tom Homberg’s blog post for a good explanation of this) and here it definitely makes sense to use a combination of @WebMvcTest
and @MockBean
.
Be aware that whenever you use @MockBean
Spring can’t reuse an already existing context that includes the actual bean of this type. This means you’ll get a fresh new context (if there isn’t a test with a similar setup) and this adds time to your test execution. If done right, you can improve your build times with Spring’s Context Caching mechanism a lot.
Are you looking for further practical resources on Mockito? Consider enrolling in my Hands-On With Mockito Online Course to learn the ins and outs of the most popular mocking library for JVM applications.
You can find the source code for this @Mock vs. @MockBean example when testing Spring Boot applications on GitHub.
Have fun mocking your collaborators with either @Mock or @MockBean,