首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Mockito入门教程:让Java单元测试变得简单有趣

Mockito入门教程:让Java单元测试变得简单有趣

原创
作者头像
用户11856829
发布2025-10-04 10:38:08
发布2025-10-04 10:38:08
480
举报

什么是Mockito?为什么你需要它

想象一下,你正在开发一个电商系统。用户下单时需要调用支付接口、库存系统、短信通知等多个外部服务。如果每次跑单元测试都要连接这些真实服务,那简直是噩梦!!!

这就是Mockito出场的时候了。它是Java生态中最受欢迎的模拟测试框架,能够创建虚假的依赖对象(Mock对象),让你的测试变得快速、独立、可控。

Mockito的核心理念很简单:隔离被测试的代码。通过模拟外部依赖,我们只关注当前类的业务逻辑是否正确。

快速上手:第一个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:创建模拟对象

@Mock注解是Mockito的基础,它为指定的类创建一个模拟对象。这个对象的所有方法默认返回null、0或false等默认值。

```java @Mock private UserRepository userRepository;

@Mock private EmailService emailService; ```

@InjectMocks:依赖注入

@InjectMocks会自动将@Mock创建的对象注入到被测试的类中。Mockito会尝试通过构造函数、setter方法或字段注入来完成这个过程。

java @InjectMocks private UserService userService; // 自动注入userRepository

@Spy:部分模拟

有时候你想保留真实对象的大部分行为,只模拟其中几个方法。这时候用@Spy:

```java @Spy private List spyList = new ArrayList<>();

@Test void testSpy() { spyList.add("真实元素");

} ```

when-then:定义模拟行为的艺术

基础用法

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:验证交互的利器

光定义行为还不够,我们还需要验证模拟对象是否被正确调用了。这就是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 {

} ```

进阶技巧:让测试更优雅

使用Answer进行复杂模拟

有时候简单的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方法

对于void方法,我们主要关注是否正确调用以及参数是否正确:

```java @Test void testVoidMethod() { doNothing().when(emailService).sendEmail(anyString());

}

// 模拟void方法抛异常 @Test void testVoidMethodException() { doThrow(new EmailException("邮件服务不可用")) .when(emailService).sendEmail(anyString());

} ```

常见陷阱与最佳实践

陷阱1:过度Mock

不要什么都Mock!值对象、简单的工具类不需要Mock。只Mock那些有外部依赖或者复杂逻辑的对象。

```java // 错误:不需要Mock简单值对象 @Mock private User user; // User只是个数据容器

// 正确:Mock有业务逻辑的服务 @Mock private UserService userService; ```

陷阱2:忘记重置Mock

在使用@MockitoSettings(strictness = Strictness.STRICT_STUBS)时,未使用的stub会导致测试失败。及时清理或使用合适的注解。

陷阱3:验证过多

不要验证每一个方法调用,重点验证关键的业务交互:

```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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是Mockito?为什么你需要它
  • 快速上手:第一个Mockito测试
    • 添加依赖
    • 一个简单的例子
  • 核心注解详解
    • @Mock:创建模拟对象
    • @InjectMocks:依赖注入
    • @Spy:部分模拟
  • when-then:定义模拟行为的艺术
    • 基础用法
    • 参数匹配器
    • 连续调用
  • verify:验证交互的利器
    • 基础验证
    • 调用顺序验证
    • 参数捕获
  • 实战案例:电商订单系统测试
  • 进阶技巧:让测试更优雅
    • 使用Answer进行复杂模拟
    • 模拟静态方法
    • 测试void方法
  • 常见陷阱与最佳实践
    • 陷阱1:过度Mock
    • 陷阱2:忘记重置Mock
    • 陷阱3:验证过多
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档