@Mock vs. @MockBean When Testing Spring Boot Applications

Last Updated:  August 27, 2021 | Published: November 10, 2020

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.

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.

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:

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 StockServiceas 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:

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).

A simplified visualization of the Spring Test Context when mocking a bean looks like this:

Spring Context @MockBean example

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,

Philip

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

    Join Our Mailing List To Get 3x Free Cheat Sheets

    Free Java Cheat Sheets
    >