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 注解功能:
java">@ExtendWith(MockitoExtension.class) // JUnit 5 必加
public class UserServiceTest {// 测试代码...
}
2.2 自动初始化注解
无需手动调用 MockitoAnnotations.openMocks(this)
,@ExtendWith
已自动处理。
3. 注解使用详解
3.1 @Mock
注解
作用:创建完全模拟的依赖对象。
示例场景:
java">public class UserService {private final UserDao userDao;public UserService(UserDao userDao) {this.userDao = userDao;}public User getUserById(int id) {return userDao.findById(id);}
}
测试代码:
java">@ExtendWith(MockitoExtension.class)
class UserServiceTest {@Mockprivate UserDao mockUserDao; // 自动创建 Mock 对象@InjectMocksprivate UserService userService; // 自动注入 mockUserDao@Testvoid 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
注解
作用:创建部分模拟对象,保留真实方法逻辑,除非显式覆盖。
示例场景:
java">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;}
}
测试代码:
java">@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {@Spy // 部分模拟,保留真实方法private PaymentService spyPaymentService;@Testvoid 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
对象注入到被测类中。
注入规则:
- 构造器注入(优先):匹配参数类型和数量。
- Setter 注入:调用 setter 方法。
- 字段注入(最后):直接反射注入字段。
示例:
java">@ExtendWith(MockitoExtension.class)
class OrderServiceTest {@Mockprivate InventoryService inventoryService;@Mockprivate PaymentService paymentService;@InjectMocks // 自动注入 inventoryService 和 paymentServiceprivate OrderService orderService;@Testvoid placeOrder_ShouldCheckInventory() {when(inventoryService.checkStock(anyString())).thenReturn(true);orderService.placeOrder("product_123");verify(inventoryService).checkStock("product_123");}
}
3.4 @Captor
注解
作用:自动创建参数捕获器,简化参数验证。
示例:
java">@ExtendWith(MockitoExtension.class)
class NotificationServiceTest {@Mockprivate EmailClient mockEmailClient;@InjectMocksprivate NotificationService notificationService;@Captor // 自动初始化 ArgumentCaptorprivate ArgumentCaptor<EmailRequest> emailCaptor;@Testvoid 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:
java">@SpringBootTest
public class ProductServiceIntegrationTest {@MockBean // Spring 管理的 Mockprivate InventoryService mockInventoryService;@Autowiredprivate ProductService productService;@Testvoid reserveProduct_ShouldUseMockInventory() {when(mockInventoryService.reserve(anyString())).thenReturn(true);boolean result = productService.reserveProduct("product_123");assertTrue(result);}
}
6. 最佳实践
- 保持测试简洁:使用注解减少手动初始化代码。
- 明确依赖关系:通过
@InjectMocks
明确被测类的依赖注入方式。 - 避免过度 Mock:仅 Mock 外部依赖,保留核心逻辑的真实性。
- 结合 AssertJ:使用流式断言提高测试可读性:
java">assertThat(capturedEmail.getSubject()).contains("Welcome");
通过合理使用 Mockito 注解,可以显著提升单元测试的编写效率和可维护性。