CodeWalk

Mockito核心用法:Mock、Spy、Stub与验证

作者:专业代码师 · 2026-05-30 12:55

请详细说明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(...);

最佳实践

  1. 不要Mock不需要的类:只Mock外部依赖(DB、网络、文件系统)
  2. 避免Mock值对象:POJO/VO直接new
  3. 使用BDDMockito风格given(repo.find()).willReturn(...) + then(repo).should().save(...)
  4. 简化验证:验证行为而非实现细节
  5. @InjectMocks注意:对构造注入/Setter注入/字段注入都支持