Mockito核心用法:Mock、Spy、Stub与验证
请详细说明Mockito的核心用法,包括:创建Mock对象(@Mock/@InjectMocks)、打桩(when/thenReturn/thenThrow/thenAnswer)、验证(verify)、Spy部分模拟、ArgumentMatchers和Answer自定义。编写高质量单元测试的最佳实践是什么?
回答
专业代码师
创建Mock
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@InjectMocks
private UserService userService; // 自动注入Mock
}
打桩(Stubbing)
// 返回值
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
// 抛出异常
when(userRepository.save(any())).thenThrow(new DataIntegrityViolationException("重复"));
// 多重返回
when(iterator.next()).thenReturn("first").thenReturn("second").thenThrow();
// Answer(根据参数动态返回)
when(repository.findByName(anyString()))
.thenAnswer(invocation -> {
String name = invocation.getArgument(0);
return new User(name);
});
验证(Verification)
verify(userRepository).save(any(User.class)); // 默认times(1)
verify(userRepository, times(2)).findById(1L); // 精确次数
verify(userRepository, never()).delete(any()); // 从未调用
verify(userRepository, atLeast(1)).findAll(); // 至少一次
verifyNoMoreInteractions(userRepository); // 无额外调用
verify(userRepository, timeout(100).times(1)).save(any()); // 超时+次数
Spy部分模拟
@Spy
private List<String> list = new ArrayList<>();
// 部分方法打桩
doReturn("mocked").when(list).get(0);
// 真实方法:list.add("a") 会实际执行
ArgumentMatchers
// 任意匹配
when(repo.findById(anyLong())).thenReturn(...);
when(repo.findByName(anyString())).thenReturn(...);
when(repo.findByAge(gt(18))).thenReturn(...); // greater than
// 自定义匹配器
when(repo.findByName(argThat(name -> name.startsWith("A")))).thenReturn(...);
最佳实践
- 不要Mock不需要的类:只Mock外部依赖(DB、网络、文件系统)
- 避免Mock值对象:POJO/VO直接new
- 使用BDDMockito风格:
given(repo.find()).willReturn(...)+then(repo).should().save(...) - 简化验证:验证行为而非实现细节
- @InjectMocks注意:对构造注入/Setter注入/字段注入都支持