想象一下,你正在开发一个电商系统。用户下单时需要调用支付接口、库存系统、短信通知等多个外部服务。如果每次跑单元测试都要连接这些真实服务,那简直是噩梦!!!
这就是Mockito出场的时候了。它是Java生态中最受欢迎的模拟测试框架,能够创建虚假的依赖对象(Mock对象),让你的测试变得快速、独立、可控。
Mockito的核心理念很简单:隔离被测试的代码。通过模拟外部依赖,我们只关注当前类的业务逻辑是否正确。
首先在你的pom.xml中添加Mockito依赖:
xml <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.6.0</version> <scope>test</scope> </dependency>
如果你使用JUnit 5,还需要添加mockito-junit-jupiter:
xml <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>5.6.0</version> <scope>test</scope> </dependency>
假设我们有一个用户服务类:
```java public class UserService { private UserRepository userRepository;
} ```
现在用Mockito来测试这个方法:
```java import static org.mockito.Mockito.; import static org.junit.jupiter.api.Assertions.;
@ExtendWith(MockitoExtension.class) class UserServiceTest {
} ```
看到了吗?我们完全没有连接真实的数据库,但测试依然能够验证业务逻辑的正确性。
@Mock注解是Mockito的基础,它为指定的类创建一个模拟对象。这个对象的所有方法默认返回null、0或false等默认值。
```java @Mock private UserRepository userRepository;
@Mock private EmailService emailService; ```
@InjectMocks会自动将@Mock创建的对象注入到被测试的类中。Mockito会尝试通过构造函数、setter方法或字段注入来完成这个过程。
java @InjectMocks private UserService userService; // 自动注入userRepository
有时候你想保留真实对象的大部分行为,只模拟其中几个方法。这时候用@Spy:
```java @Spy private List spyList = new ArrayList<>();
@Test void testSpy() { spyList.add("真实元素");
} ```
when().thenReturn()是Mockito的核心语法,用来定义模拟对象的行为:
```java // 简单返回值 when(userRepository.findById(1L)).thenReturn(new User("张三"));
// 返回null when(userRepository.findById(999L)).thenReturn(null);
// 抛出异常 when(userRepository.findById(-1L)).thenThrow(new IllegalArgumentException("用户ID不能为负数")); ```
Mockito提供了强大的参数匹配器,让你能够更灵活地定义行为:
```java // 匹配任意Long类型参数 when(userRepository.findById(anyLong())).thenReturn(defaultUser);
// 匹配特定条件 when(userRepository.findById(longThat(id -> id > 100))).thenReturn(vipUser);
// 匹配字符串 when(userRepository.findByName(startsWith("张"))).thenReturn(zhangUsers); ```
有时候同一个方法在不同次调用时需要返回不同结果:
java when(randomService.getRandomNumber()) .thenReturn(1) // 第一次调用返回1 .thenReturn(2) // 第二次调用返回2 .thenThrow(new RuntimeException("随机数生成失败")); // 第三次调用抛异常
光定义行为还不够,我们还需要验证模拟对象是否被正确调用了。这就是verify的作用!!!
```java // 验证方法被调用了一次 verify(userRepository).findById(1L);
// 验证方法从未被调用 verify(userRepository, never()).deleteById(anyLong());
// 验证调用次数 verify(emailService, times(2)).sendEmail(anyString());
// 验证至少调用了一次 verify(logService, atLeastOnce()).log(anyString()); ```
有时候方法调用的顺序很重要:
```java InOrder inOrder = inOrder(userRepository, emailService);
inOrder.verify(userRepository).findById(1L); inOrder.verify(emailService).sendWelcomeEmail("张三"); ```
想要验证传递给模拟对象的具体参数?用ArgumentCaptor:
```java @Captor private ArgumentCaptor userCaptor;
@Test void shouldSaveUserWithCorrectInfo() { userService.createUser("张三", "zhang@example.com");
} ```
让我们通过一个更复杂的例子来巩固所学知识。假设我们有一个订单服务:
```java public class OrderService { private PaymentService paymentService; private InventoryService inventoryService; private NotificationService notificationService;
} ```
对应的测试类:
```java @ExtendWith(MockitoExtension.class) class OrderServiceTest {
} ```
有时候简单的thenReturn不够用,我们需要根据输入参数计算返回值:
```java when(calculatorService.calculate(anyDouble(), anyDouble(), anyString())) .thenAnswer(invocation -> { double a = invocation.getArgument(0); double b = invocation.getArgument(1); String operation = invocation.getArgument(2);
```
Mockito 3.4+开始支持静态方法模拟:
```java @Test void testStaticMethod() { try (MockedStatic mockedStatic = mockStatic(DateUtils.class)) { mockedStatic.when(() -> DateUtils.getCurrentDate()) .thenReturn(LocalDate.of(2024, 1, 1));
} ```
对于void方法,我们主要关注是否正确调用以及参数是否正确:
```java @Test void testVoidMethod() { doNothing().when(emailService).sendEmail(anyString());
}
// 模拟void方法抛异常 @Test void testVoidMethodException() { doThrow(new EmailException("邮件服务不可用")) .when(emailService).sendEmail(anyString());
} ```
不要什么都Mock!值对象、简单的工具类不需要Mock。只Mock那些有外部依赖或者复杂逻辑的对象。
```java // 错误:不需要Mock简单值对象 @Mock private User user; // User只是个数据容器
// 正确:Mock有业务逻辑的服务 @Mock private UserService userService; ```
在使用@MockitoSettings(strictness = Strictness.STRICT_STUBS)时,未使用的stub会导致测试失败。及时清理或使用合适的注解。
不要验证每一个方法调用,重点验证关键的业务交互:
```java // 过度验证 verify(logger).debug("开始处理用户请求"); verify(validator).validate(user); verify(logger).debug("验证通过"); // ... 更多不重要的验证
// 恰当验证 verify(userRepository).save(user); // 关键业务操作 verify(emailService).sendWelcomeEmail(user.getEmail()); // 重要副作用 ```
Mockito让Java单元测试变得简单而强大。通过模拟外部依赖,我们能够:
记住几个核心概念:用@Mock创建模拟对象,用when-then定义行为,用verify验证交互,用@InjectMocks注入依赖。掌握了这些,你就能写出优雅、可靠的单元测试。
开始在你的项目中使用Mockito吧!它会让你的测试代码变得更加专业和可维护。测试不再是负担,而是开发过程中的得力助手。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。