1. Overview

In this quick tutorial, we’ll explore the usage of the Spring Boot @MockBeans annotation.

2. Example Setup

Before we dive in, let’s create a simple ticket validator example we’ll use throughout this tutorial:

public class TicketValidator {
    private CustomerRepository customerRepository;

    private TicketRepository ticketRepository;

    public boolean validate(Long customerId, String code) {
        customerRepository.findById(customerId)
          .orElseThrow(() -> new RuntimeException("Customer not found"));

        ticketRepository.findByCode(code)
          .orElseThrow(() -> new RuntimeException("Ticket with given code not found"));
        return true;
    }
}

Here, we defined the validate() method that checks whether a given data exists in the database. It uses CustomerRepository and TicketRepository as dependencies.

Now, let’s examine how to create a test and mock dependencies using Spring’s @MockBean and @MockBeans annotations.

3. The @MockBean Annotation

Spring framework provides the @MockBean annotation to mock dependencies for testing purposes. This annotation allows us to define a mocked version of a specific bean. A newly created mock will be added to the Spring ApplicationContext. Consequently, if a bean of the same type already exists, it’ll be replaced with the mocked version.

Furthermore, we can use this annotation on a field we’d like to mock or on a test class level.

Using the @MockBean annotation, we can isolate the specific part of the code we want to test by mocking the behavior of its dependent objects.

Now, let’s see the @MockBean in action. Let’s replace an existing CustomerRepository bean with a mock implementation:

class MockBeanTicketValidatorUnitTest {
    @MockBean
    private CustomerRepository customerRepository;

    @Autowired
    private TicketRepository ticketRepository;

    @Autowired
    private TicketValidator ticketValidator;

    @Test
    void givenUnknownCustomer_whenValidate_thenThrowException() {
        String code = UUID.randomUUID().toString();
        when(customerRepository.findById(any())).thenReturn(Optional.empty());

        assertThrows(RuntimeException.class, () -> ticketValidator.validate(1L, code));
    }
}

Here, we annotated the CustomerRepository field with the @MockBean annotation. Spring injects the mock into the field and adds it to the application context.

One thing to keep in mind is that we can’t use the @MockBean annotation to mock a bean’s behavior during the application context refresh.

Furthermore, this annotation is defined as @Repeatable, which allows us to define the same annotation multiple times on the class level:

@MockBean(CustomerRepository.class)
@MockBean(TicketRepository.class)
@SpringBootTest(classes = Application.class)
class MockBeanTicketValidatorUnitTest {
    @Autowired
    private CustomerRepository customerRepository;

    @Autowired
    private TicketRepository ticketRepository;

    @Autowired
    private TicketValidator ticketValidator;

    // ...
}

4. The @MockBeans Annotation

Now that we’ve discussed the @MockBean annotation, let’s move on to the @MockBeans annotation. Simply put, this annotation represents an aggregation of multiple @MockBean annotations and serves as a container for them.

Additionally, it helps us organize test cases. We can define multiple mocks in the same place, making the test class cleaner and more organized. Moreover, it can be useful when reusing mocked beans across numerous test classes.

We can use the @MockBeans as an alternative to the repeatable @MockBean solution we saw earlier:

@MockBeans({@MockBean(CustomerRepository.class), @MockBean(TicketRepository.class)})
@SpringBootTest(classes = Application.class)
class MockBeansTicketValidatorUnitTest {
    @Autowired
    private CustomerRepository customerRepository;

    @Autowired
    private TicketRepository ticketRepository;

    @Autowired
    private TicketValidator ticketValidator;

    // ...
}

It’s worth noting that we used the @Autowired annotation for the beans we wanted to mock.

Moreover, there’s no difference in functionality between this approach and defining the @MockBean on each field. However, if we’re using Java 8 or higher, the @MockBeans annotation might seem redundant because Java supports repeatable annotations.

The main idea behind the @MockBeans annotation is to allow developers to specify mock beans in one place.

5. Conclusion

In this short article, we learned how to use the @MockBeans annotation while defining mocks for testing.

To summarize, we can use the @MockBeans annotation to group multiple @MockBean annotations and define all mocks in one place.

As always, the entire source code of examples is available over on GitHub.