Mocking with (and without) Spring Boot

Table Of Contents

Mockito is a very popular library to support testing. It allows us to replace real objects with “mocks”, i.e. with objects that are not the real thing and whose behavior we can control within our test.

This article gives a quick intro to the how and why of Mockito and Spring Boot’s integration with it.

Example Code

This article is accompanied by a working code example on GitHub.

The System Under Test

Before we dive into the details of mocking, let’s take a look at the application we’re going to test. We’ll use some code based on the payment example application “buckpal” of my book.

The system under test for this article will be a Spring REST controller that accepts requests to transfer money from one account to another:

@RestController
@RequiredArgsConstructor
public class SendMoneyController {

  private final SendMoneyUseCase sendMoneyUseCase;

  @PostMapping(path = "/sendMoney/{sourceAccountId}/{targetAccountId}/{amount}")
  ResponseEntity sendMoney(
          @PathVariable("sourceAccountId") Long sourceAccountId,
          @PathVariable("targetAccountId") Long targetAccountId,
          @PathVariable("amount") Integer amount) {
  
    SendMoneyCommand command = new SendMoneyCommand(
            sourceAccountId,
            targetAccountId,
            amount);
  
    boolean success = sendMoneyUseCase.sendMoney(command);
    
    if (success) {
      return ResponseEntity
              .ok()
              .build();
    } else {
      return ResponseEntity
              .status(HttpStatus.INTERNAL_SERVER_ERROR)
              .build();
    }
  }

}

The controller passes the input on to an instance of SendMoneyUseCase which is an interface with a single method:

public interface SendMoneyUseCase {

  boolean sendMoney(SendMoneyCommand command);

  @Value
  @Getter
  @EqualsAndHashCode(callSuper = false)
  class SendMoneyCommand {

    private final Long sourceAccountId;
    private final Long targetAccountId;
    private final Integer money;

    public SendMoneyCommand(
            Long sourceAccountId,
            Long targetAccountId,
            Integer money) {
      this.sourceAccountId = sourceAccountId;
      this.targetAccountId = targetAccountId;
      this.money = money;
    }
  }

}

Finally, we have a dummy service implementing the SendMoneyUseCase interface:

@Slf4j
@Component
public class SendMoneyService implements SendMoneyUseCase {

  public SendMoneyService() {
    log.info(">>> constructing SendMoneyService! <<<");
  }

  @Override
  public boolean sendMoney(SendMoneyCommand command) {
    log.info("sending money!");
    return false;
  }

}

Imagine that there is some wildly complicated business logic going on in this class in place of the logging statements.

For most of this article, we’re not interested in the actual implementation of the SendMoneyUseCase interface. After all, we want to mock it away in our test of the web controller.

Why Mock?

Why should we use a mock instead of a real service object in a test?

Imagine the service implementation above has a dependency to a database or some other third-party system. We don’t want to have our test run against the database. If the database isn’t available, the test will fail even though our system under test might be completely bug-free. The more dependencies we add in a test, the more reasons a test has to fail. And most of those reasons will be the wrong ones. If we use a mock instead, we can mock all those potential failures away.

Aside from reducing failures, mocking also reduces our tests' complexity and thus saves us some effort. It takes a lot of boilerplate code to set up a whole network of correctly-initialized objects to be used in a test. Using mocks, we only have to “instantiate” one mock instead of a whole rat-tail of objects the real object might need to be instantiated.

In summary, we want to move from a potentially complex, slow, and flaky integration test towards a simple, fast, and reliable unit test.

So, in a test of our SendMoneyController above, instead of a real instance of SendMoneyUseCase, we want to use a mock with the same interface whose behavior we can control as needed in the test.

Mocking with Mockito (and without Spring)

As a mocking framework, we’ll use Mockito, since it’s well-rounded, well-established, and well-integrated into Spring Boot.

But the best kind of test doesn’t use Spring at all, so let’s first look at how to use Mockito in a plain unit test to mock away unwanted dependencies.

Plain Mockito Test

The plainest way to use Mockito is to simply instantiate a mock object using Mockito.mock() and then pass the so created mock object into the class under test:

public class SendMoneyControllerPlainTest {

  private SendMoneyUseCase sendMoneyUseCase = 
      Mockito.mock(SendMoneyUseCase.class);

  private SendMoneyController sendMoneyController = 
      new SendMoneyController(sendMoneyUseCase);

  @Test
  void testSuccess() {
    // given
    SendMoneyCommand command = new SendMoneyCommand(1L, 2L, 500);
    given(sendMoneyUseCase
        .sendMoney(eq(command)))
        .willReturn(true);
  
    // when
    ResponseEntity response = sendMoneyController
        .sendMoney(1L, 2L, 500);
  
    // then
    then(sendMoneyUseCase)
        .should()
        .sendMoney(eq(command));
  
    assertThat(response.getStatusCode())
        .isEqualTo(HttpStatus.OK);
  }

}

We create a mock instance of SendMoneyService and pass this mock into the constructor of SendMoneyController. The controller doesn’t know that it’s a mock and will treat it just like the real thing.

In the test itself, we can use Mockito’s given() to define the behavior we want the mock to have and then() to check if certain methods have been called as expected. You can find more on Mockito’s mocking and verification methods in the docs.

Web Controllers Should Be Integration-tested!

Don't do this at home! The code above is just an example for how to create mocks. Testing a Spring Web Controller with a unit test like this only covers a fraction of the potential errors that can happen in production. The unit test above verifies that a certain response code is returned, but it does not integrate with Spring to check if the input parameters are parsed correctly from an HTTP request, or if the controller listens to the correct path, or if exceptions are transformed into the expected HTTP response, and so on.

Web controllers should instead be tested in integration with Spring as discussed in my article about the @WebMvcTest annotation.

Using Mockito Annotations with JUnit Jupiter

Mockito provides some handy annotations that reduce the manual work of creating mock instances and passing them into the object we’re about to test.

With JUnit Jupiter, we need to apply the MockitoExtension to our test:

@ExtendWith(MockitoExtension.class)
class SendMoneyControllerMockitoAnnotationsJUnitJupiterTest {

  @Mock
  private SendMoneyUseCase sendMoneyUseCase;

  @InjectMocks
  private SendMoneyController sendMoneyController;

  @Test
  void testSuccess() {
    ...
  }

}

We can then use the @Mock and @InjectMocks annotations on fields of the test.

Fields annotated with @Mock will then automatically be initialized with a mock instance of their type, just like as we would call Mockito.mock() by hand.

Mockito will then try to instantiate fields annotated with @InjectMocks by passing all mocks into a constructor. Note that we need to provide such a constructor for Mockito to work reliably. If Mockito doesn’t find a constructor, it will try setter injection or field injection, but the cleanest way is still a constructor. You can read about the algorithm behind this in Mockito’s Javadoc.

Using Mockito Annotations with JUnit 4

With JUnit 4, it’s very similar, except that we need to use MockitoJUnitRunner instead of MockitoExtension:

@RunWith(MockitoJUnitRunner.class)
public class SendMoneyControllerMockitoAnnotationsJUnit4Test {

  @Mock
  private SendMoneyUseCase sendMoneyUseCase;

  @InjectMocks
  private SendMoneyController sendMoneyController;

  @Test
  public void testSuccess() {
    ...
  }

}

Mocking with Mockito and Spring Boot

There are times when we have to rely on Spring Boot to set up an application context for us because it would be too much work to instantiate the whole network of classes manually.

We may not want to test the integration between all the beans in a certain test, however, so we need a way to replace certain beans within Spring’s application context with a mock. Spring Boot provides the @MockBean and @SpyBean annotations for this purpose.

Adding a Mock Spring Bean with @MockBean

A prime example for using mocks is using Spring Boot’s @WebMvcTest to create an application context that contains all the beans necessary for testing a Spring web controller:

@WebMvcTest(controllers = SendMoneyController.class)
class SendMoneyControllerWebMvcMockBeanTest {

  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private SendMoneyUseCase sendMoneyUseCase;

  @Test
  void testSendMoney() {
    ...
  }

}

The application context created by @WebMvcTest will not pick up our SendMoneyService bean (which implements the SendMoneyUseCase interface), even though it is marked as a Spring bean with the @Component annotation. We have to provide a bean of type SendMoneyUseCase ourselves, otherwise, we’ll get an error like this:

No qualifying bean of type 'io.reflectoring.mocking.SendMoneyUseCase' available:
  expected at least 1 bean which qualifies as autowire candidate.

Instead of instantiating SendMoneyService ourselves or telling Spring to pick it up, potentially pulling in a rat-tail of other beans in the process, we can just add a mock implementation of SendMoneyUseCase to the application context.

This is easily done by using Spring Boot’s @MockBean annotation. The Spring Boot test support will then automatically create a Mockito mock of type SendMoneyUseCase and add it to the application context so that our controller can use it. In the test method, we can then use Mockito’s given() and when() methods just like above.

This way we can easily create a focused web controller test that instantiates only the objects it needs.

Replacing a Spring Bean with @MockBean

Instead of adding a new (mock) bean, we can use @MockBean similarly to replace a bean that already exists in the application context with a mock:

@SpringBootTest
@AutoConfigureMockMvc
class SendMoneyControllerSpringBootMockBeanTest {

  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private SendMoneyUseCase sendMoneyUseCase;

  @Test
  void testSendMoney() {
    ...
  }

}

Note that the test above uses @SpringBootTest instead of @WebMvcTest, meaning that the full application context of the Spring Boot application will be created for this test. This includes our SendMoneyService bean, as it is annotated with @Component and lies within the package structure of our application class.

The @MockBean annotation will cause Spring to look for an existing bean of type SendMoneyUseCase in the application context. If it exists, it will replace that bean with a Mockito mock.

The net result is the same: in our test, we can treat the sendMoneyUseCase object like a Mockito mock.

The difference is that the SendMoneyService bean will be instantiated when the initial application context is created before it’s replaced with the mock. If SendMoneyService did something in its constructor that requires a dependency to a database or third-party system that’s not available at test time, this wouldn’t work. Instead of using @SpringBootTest, we’d have to create a more focused application context and add the mock to the application context before the actual bean is instantiated.

Spying on a Spring Bean with @SpyBean

Mockito also allows us to spy on real objects. Instead of mocking away an object completely, Mockito creates a proxy around the real object and simply monitors which methods are being called to that we can later verify if a certain method has been called or not.

Spring Boot provides the @SpyBean annotation for this purpose:

@SpringBootTest
@AutoConfigureMockMvc
class SendMoneyControllerSpringBootSpyBeanTest {

  @Autowired
  private MockMvc mockMvc;

  @SpyBean
  private SendMoneyUseCase sendMoneyUseCase;

  @Test
  void testSendMoney() {
    ...
  }

}

@SpyBean works just like @MockBean. Instead of adding a bean to or replacing a bean in the application context it simply wraps the bean in Mockito’s proxy. In the test, we can then use Mockito’s then() to verify method calls just as above.

Why Do My Spring Tests Take So Long?

If we use @MockBean and @SpyBean a lot in our tests, running the tests will take a lot of time. This is because Spring Boot creates a new application context for each test, which can be an expensive operation depending on the size of the application context.

Conclusion

Mockito makes it easy for us to mock away objects that we don’t want to test right now. This allows to reduce integration overhead in our tests and can even transform an integration test into a more focused unit test.

Spring Boot makes it easy to use Mockito’s mocking features in Spring-supported integration tests by using the @MockBean and @SpyBean annotations.

As easy as these Spring Boot features are to include in our tests, we should be aware of the cost: each test may create a new application context, potentially increasing the runtime of our test suite noticeable.

The code examples are available on GitHub.

Written By:

Tom Hombergs

Written By:

Tom Hombergs

As a professional software engineer, consultant, architect, general problem solver, I've been practicing the software craft for more than fifteen years and I'm still learning something new every day. I love sharing the things I learned, so you (and future me) can get a head start. That's why I founded reflectoring.io.

Recent Posts

Guide to JUnit 5 Functional Interfaces

In this article, we will get familiar with JUnit 5 functional interfaces. JUnit 5 significantly advanced from its predecessors. Features like functional interfaces can greatly simplify our work once we grasp their functionality.

Read more

Getting Started with Spring Security and JWT

Spring Security provides a comprehensive set of security features for Java applications, covering authentication, authorization, session management, and protection against common security threats such as CSRF (Cross-Site Request Forgery).

Read more

Creating and Publishing an NPM Package with Automated Versioning and Deployment

In this step-by-step guide, we’ll create, publish, and manage an NPM package using TypeScript for better code readability and scalability. We’ll write test cases with Jest and automate our NPM package versioning and publishing process using Changesets and GitHub Actions.

Read more