Mockito 注解、插桩、验证、断言
2025-08-05 本文已影响0人
flyjar
Mockito 注解、插桩、验证、断言、mock 与 spy 等关键内容:
一、Mockito 基础与核心注解
1. 核心依赖与配置
- 需在测试类上添加
@ExtendWith(MockitoExtension.class)注解,启用 Mockito 扩展支持。 - 常用注解:
-
@Mock:创建一个完全模拟的对象(虚拟对象,无真实逻辑)。 -
@InjectMocks:自动将@Mock标注的对象注入到被测试类中(依赖注入)。 -
@Spy:创建一个对真实对象的“监视”对象(默认执行真实方法,可部分插桩)。
-
二、Mock 与 Spy 的区别
| 特性 | Mock 对象 | Spy 对象 |
|---|---|---|
| 本质 | 完全虚拟的对象(无真实实现) | 基于真实对象的监视(可复用真实逻辑) |
| 默认行为 | 未插桩方法返回空值(null、0 等) |
未插桩方法执行真实对象的逻辑 |
| 适用场景 | 完全隔离外部依赖(如数据库、网络) | 复用大部分真实逻辑,仅修改部分行为 |
| 插桩推荐方式 | when(mock.method()).thenReturn(...) |
doReturn(...).when(spy).method() |
| 副作用 | 无(不调用真实方法) | 有(可能修改真实对象状态) |
示例
@ExtendWith(MockitoExtension.class)
class MockSpyTest {
@Mock
private List<String> mockList; // 完全模拟的List
@Spy
private List<String> spyList = new ArrayList<>(); // 基于真实ArrayList的监视对象
@Test
void testMock() {
// Mock默认返回空值,需手动插桩
when(mockList.size()).thenReturn(10);
assertEquals(10, mockList.size());
}
@Test
void testSpy() {
// Spy默认执行真实方法
spyList.add("item");
assertEquals(1, spyList.size()); // 真实逻辑:size=1
// 插桩覆盖部分行为(推荐用doReturn)
doReturn(100).when(spyList).size();
assertEquals(100, spyList.size()); // 插桩生效
}
}
三、插桩(Stubbing):预设方法行为
插桩是为模拟对象预设返回值、异常或自定义行为,确保测试不受外部依赖影响。
1. 基础插桩方式
-
when(...).thenReturn(...):适用于非void方法,简洁直观。// 基本用法 when(mockService.findById(1L)).thenReturn(new User(1L, "Alice")); // 连续返回不同值 when(mockIterator.next()) .thenReturn("first") .thenReturn("second"); -
doReturn(...).when(...):适用于 spy 对象或避免触发真实方法的场景。doReturn("test").when(spyList).get(0); // 不执行真实get(0) -
模拟异常:
// 非void方法 when(mockService.findById(99L)).thenThrow(new RuntimeException("未找到")); // void方法(必须用doThrow) doThrow(new IOException()).when(mockFile).delete(); -
自定义行为(
thenAnswer):基于输入参数动态返回结果。when(calculator.add(anyInt(), anyInt())).thenAnswer(invocation -> { int a = invocation.getArgument(0); int b = invocation.getArgument(1); return a + b; });
2. 特殊场景插桩
-
void方法插桩:只能用doXxx()系列(doNothing()、doThrow()等)。doNothing().when(mockLogger).debug(anyString()); // 忽略void方法 -
参数匹配器:忽略具体参数,使用
any()、eq()、gt()等匹配任意参数。import static org.mockito.ArgumentMatchers.*; when(mockService.findByName(anyString())).thenReturn(new User()); // 任意字符串 when(mockService.validate(eq(10), gt(0))).thenReturn(true); // 第一个参数=10,第二个>0
四、验证(Verify):检查方法调用行为
验证用于确认 mock/spy 对象的方法是否按预期被调用(次数、参数等)。
常用验证方法
// 验证方法被调用过1次(默认)
verify(mockList).add("item");
// 验证调用次数
verify(mockList, times(3)).add(anyString()); // 调用3次
verify(mockList, never()).remove(any()); // 从未调用
// 验证调用顺序
InOrder inOrder = inOrder(mockService, mockRepo);
inOrder.verify(mockService).update(any());
inOrder.verify(mockRepo).save(any()); // 确保update在save之前调用
// 验证无多余调用
verifyNoMoreInteractions(mockList);
五、断言(Assertion):验证结果正确性
结合 JUnit 5 的 Assertions 工具类,验证测试结果是否符合预期。
import static org.junit.jupiter.api.Assertions.*;
@Test
void testResult() {
User user = userService.getUser(1L);
assertNotNull(user); // 非空断言
assertEquals("Alice", user.getName()); // 相等断言
assertTrue(user.getAge() > 18); // 条件断言
// 异常断言
assertThrows(RuntimeException.class, () -> {
userService.getUser(99L);
});
}
六、综合示例
测试 OrderService 依赖 ProductDao 的场景:
public class OrderService {
private final ProductDao productDao;
public OrderService(ProductDao productDao) {
this.productDao = productDao;
}
public double calculateTotal(List<Long> productIds) {
double total = 0;
for (Long id : productIds) {
total += productDao.getPrice(id);
}
return total;
}
}
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private ProductDao productDao;
@InjectMocks
private OrderService orderService;
@Test
void calculateTotal_ShouldReturnSum() {
// 1. 插桩:预设商品价格
when(productDao.getPrice(1L)).thenReturn(100.0);
when(productDao.getPrice(2L)).thenReturn(200.0);
// 2. 执行测试
double total = orderService.calculateTotal(List.of(1L, 2L));
// 3. 断言结果
assertEquals(300.0, total, 0.01);
// 4. 验证调用
verify(productDao, times(2)).getPrice(anyLong()); // 调用2次
}
}
总结
-
@Mock与@Spy:分别用于创建完全模拟对象和基于真实对象的监视对象,按需选择隔离或复用真实逻辑。 -
插桩:通过
when().thenReturn()或doReturn().when()预设方法行为,控制依赖响应。 -
验证:用
verify()确认方法调用行为,确保逻辑流程正确。 - 断言:结合 JUnit 5 验证结果,确认功能正确性。
掌握这些核心概念,可以编写简洁、可靠的单元测试,有效隔离外部依赖并验证代码逻辑。