Spring Boot @MockBean 指南

1、概览

本文将带你了解 Spring Boot @MockBeans 注解的用法。

2、示例项目

以一个简单的票据验证器(Ticket Validator)示例为例:

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;
    }
}

如上,我们定义了 validate() 方法,用于检查数据库中是否存在给定数据。它依赖 CustomerRepositoryTicketRepository

现在,来看看如何使用 Spring 的 @MockBean@MockBeans 注解创建测试和模拟依赖。

3、@MockBean 注解

Spring 框架提供了 @MockBean 注解,用于模拟依赖以进行测试。该注解允许我们定义特定 Bean 的模拟版本。新创建的模拟 Bean 将被添加到 Spring ApplicationContext 中。因此,如果已经存在相同类型的 Bean,它将被替换为模拟版本。

此外,还可以在要模拟的字段或测试类级别上使用此注解。

使用 @MockBean 注解,我们可以通过模拟其依赖对象的行为来隔离我们要测试的代码的特定部分。

现在,来看看 @MockBean 的实际应用。用一个模拟 Bean 实现替换现有的 CustomerRepository Bean:

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));
    }
}

如上,使用 @MockBean 注解注解了 CustomerRepository 字段。Spring 会将 Mock Bean 注入字段,并将其添加到 Application Context 中。

需要注意的是,在 Application Context 刷新期间,不能使用 @MockBean 注解来模拟 Bean 的行为。

此外,该注解被定义为 @Repeatable,这表示我们可以在类级别多次定义相同的注解:

@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、@MockBeans 注解

理解了 @MockBean 注解之后,再来看看 @MockBeans 注解。简单地说,这个注解代表了多个 @MockBean 注解的聚合,并充当了它们的容器。

它还能帮助我们组织测试用例。我们可以在同一个地方定义多个 Mock,使测试类更简洁、更有条理。此外,在多个测试类中重复使用 Mock Bean 时,它也非常有用。

我们可以使用 @MockBeans 来替代我们之前看到的可重复 @MockBean 解决方案:

@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;

    // ...
}

注意,我们对想要 Mock 的 Bean 使用了 @Autowired 注解。

这种方法与在每个字段上定义 @MockBean 在功能上没有区别。不过,如果我们使用的是 Java 8 或更高版本,@MockBeans 注解可能就显得多余了,因为 Java 支持可重复的注解。

@MockBeans 注解背后的主要理念是允许开发人员在一个地方指定 Mock Bean。

5、总结

本文介绍了如何在测试时使用 @MockBean 注解定义 Mock Bean,以及如何使用 @MockBeans 注解将多个 @MockBean 注解分组,并在一个地方定义所有的模拟对象。


Ref:https://www.baeldung.com/java-spring-mockbeans