当前位置: 首页 > article >正文

2.5 使用注解进行单元测试详解


Mockito 使用注解进行单元测试详解

Mockito 提供了一系列注解来简化测试代码的编写,减少手动创建和管理 Mock 对象的样板代码。结合 JUnit 5,可以更高效地构建清晰、易维护的单元测试。


1. 核心注解概览
注解作用
@Mock创建并注入一个 Mock 对象(完全模拟,方法默认返回空或默认值)。
@Spy创建并注入一个 Spy 对象(部分模拟,默认调用真实方法,除非显式覆盖)。
@InjectMocks自动将 @Mock@Spy 对象注入到被测类中(依赖注入)。
@Captor自动初始化 ArgumentCaptor,用于捕获方法参数。
@ExtendWith启用 Mockito 扩展(JUnit 5 必需),替代旧版 @RunWith

2. 注解配置与启用
2.1 启用 Mockito 支持

在测试类上添加 @ExtendWith(MockitoExtension.class),激活 Mockito 注解功能:

@ExtendWith(MockitoExtension.class) // JUnit 5 必加
public class UserServiceTest {
    // 测试代码...
}
2.2 自动初始化注解

无需手动调用 MockitoAnnotations.openMocks(this)@ExtendWith 已自动处理。


3. 注解使用详解
3.1 @Mock 注解

作用:创建完全模拟的依赖对象。

示例场景

public class UserService {
    private final UserDao userDao;
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
    
    public User getUserById(int id) {
        return userDao.findById(id);
    }
}

测试代码

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserDao mockUserDao; // 自动创建 Mock 对象

    @InjectMocks
    private UserService userService; // 自动注入 mockUserDao

    @Test
    void getUserById_ShouldReturnUser() {
        // 配置 Mock 行为
        when(mockUserDao.findById(1)).thenReturn(new User(1, "Alice"));

        // 调用被测方法
        User user = userService.getUserById(1);

        // 验证结果
        assertEquals("Alice", user.getName());
        verify(mockUserDao).findById(1);
    }
}
3.2 @Spy 注解

作用:创建部分模拟对象,保留真实方法逻辑,除非显式覆盖。

示例场景

public class PaymentService {
    public boolean validateCard(String cardNumber) {
        return cardNumber != null && cardNumber.length() == 16;
    }

    public boolean processPayment(String cardNumber) {
        if (!validateCard(cardNumber)) return false;
        // 真实支付逻辑...
        return true;
    }
}

测试代码

@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {

    @Spy // 部分模拟,保留真实方法
    private PaymentService spyPaymentService;

    @Test
    void processPayment_ShouldUseMockedValidation() {
        // 覆盖 validateCard 方法
        doReturn(true).when(spyPaymentService).validateCard(anyString());

        // 调用被测方法(processPayment 会调用被覆盖的 validateCard)
        boolean result = spyPaymentService.processPayment("invalid_card");

        assertTrue(result);
        verify(spyPaymentService).validateCard("invalid_card");
    }
}
3.3 @InjectMocks 注解

作用:自动将 @Mock@Spy 对象注入到被测类中。

注入规则

  1. 构造器注入(优先):匹配参数类型和数量。
  2. Setter 注入:调用 setter 方法。
  3. 字段注入(最后):直接反射注入字段。

示例

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private InventoryService inventoryService;

    @Mock
    private PaymentService paymentService;

    @InjectMocks // 自动注入 inventoryService 和 paymentService
    private OrderService orderService;

    @Test
    void placeOrder_ShouldCheckInventory() {
        when(inventoryService.checkStock(anyString())).thenReturn(true);
        orderService.placeOrder("product_123");
        verify(inventoryService).checkStock("product_123");
    }
}
3.4 @Captor 注解

作用:自动创建参数捕获器,简化参数验证。

示例

@ExtendWith(MockitoExtension.class)
class NotificationServiceTest {

    @Mock
    private EmailClient mockEmailClient;

    @InjectMocks
    private NotificationService notificationService;

    @Captor // 自动初始化 ArgumentCaptor
    private ArgumentCaptor<EmailRequest> emailCaptor;

    @Test
    void sendWelcomeEmail_ShouldCaptureEmailContent() {
        notificationService.sendWelcomeEmail("user@example.com");

        verify(mockEmailClient).send(emailCaptor.capture());
        EmailRequest captured = emailCaptor.getValue();
        
        assertEquals("user@example.com", captured.getTo());
        assertTrue(captured.getSubject().contains("Welcome"));
    }
}

4. 常见问题与解决方案
问题解决方案
@Mock 对象为 null检查是否添加 @ExtendWith(MockitoExtension.class)
依赖注入失败确保 @InjectMocks 类的依赖项有对应的 @Mock@Spy 对象。
Spy 对象调用真实方法导致异常使用 doReturn().when() 替代 when().thenReturn() 避免执行真实方法。
参数捕获器未初始化使用 @Captor 替代手动创建 ArgumentCaptor

5. 高级整合:与 Spring Boot 测试结合

在 Spring Boot 测试中,可使用 @MockBean 替换容器中的 Bean:

@SpringBootTest
public class ProductServiceIntegrationTest {

    @MockBean // Spring 管理的 Mock
    private InventoryService mockInventoryService;

    @Autowired
    private ProductService productService;

    @Test
    void reserveProduct_ShouldUseMockInventory() {
        when(mockInventoryService.reserve(anyString())).thenReturn(true);
        boolean result = productService.reserveProduct("product_123");
        assertTrue(result);
    }
}

6. 最佳实践
  1. 保持测试简洁:使用注解减少手动初始化代码。
  2. 明确依赖关系:通过 @InjectMocks 明确被测类的依赖注入方式。
  3. 避免过度 Mock:仅 Mock 外部依赖,保留核心逻辑的真实性。
  4. 结合 AssertJ:使用流式断言提高测试可读性:
    assertThat(capturedEmail.getSubject()).contains("Welcome");
    

通过合理使用 Mockito 注解,可以显著提升单元测试的编写效率和可维护性。


http://www.kler.cn/a/543968.html

相关文章:

  • Java 同步锁性能的最佳实践:从理论到实践的完整指南
  • 【图片转换PDF】多个文件夹里图片逐个批量转换成多个pdf软件,子文件夹单独合并转换,子文件夹单独批量转换,基于Py的解决方案
  • w~自动驾驶~合集17
  • VSCode中出现“#include错误,请更新includePath“问题,解决方法
  • 香港中文大学 Adobe 推出 MotionCanvas:开启用户掌控的电影级图像视频创意之旅。
  • 什么是Java虚拟机(JVM)?它的作用是什么?
  • 【Prometheus】如何通过prometheus监控redis实时运行状态,并实现告警通知
  • 股指期货入门指南:股指期货的交割流程与机制
  • 域名解析错误—网络访问的“迷路”危机与应对指南
  • Redis 安装全攻略:在线、离线与交叉编译
  • Django开发入门 – 4.创建Django app
  • React(5)
  • lvs的DR模式
  • 企业级高并发全链路优化:流量分发、边缘防护与服务治理的整合之道
  • 【C++八股】静态局部变量/全局变量/局部变量的区别和使用场景
  • Redis混合持久化
  • 细说STM32F407单片机RTC的基本原理及闹钟和周期唤醒功能的使用方法
  • 在Windows 7操作系统,基于llama.cpp本地化部署 deepseek-r1模型的方法 2025-02-08
  • SQL-leetcode—1393. 股票的资本损益
  • 如何使用智能化RFID管控系统,对涉密物品进行安全有效的管理?
  • 计算机网络-MPLS基础概念
  • C++ ——基础进阶
  • Seaweedfs(master volume filer) docker run参数帮助文档
  • 性能优化中的服务器与操作系统优化
  • 华为云kubernetes基于keda自动伸缩deployment副本(监听redis队列长度)
  • 在亚马逊云科技上一键自动部署Falcon3大语言模型