Java Mock 测试框架 Mockito
什么是 Mock 测试?
在软件开发中,单元测试是验证代码功能的重要手段。然而,当我们的代码依赖于外部系统(如数据库、网络服务等)时,直接进行测试会面临几个问题:
- 外部依赖可能不稳定或不可用
- 测试执行速度变慢
- 难以模拟各种边界条件
Mock 测试(模拟测试)通过创建对象的"替身"来解决这些问题。Mock 对象可以:
- 模拟真实对象的行为
- 验证交互是否按预期发生
- 不执行真实对象的实际逻辑
为什么选择 Mockito?
Mockito 是目前 Java 生态中最流行的 Mock 测试框架,它具有以下优势:
- 简洁的 API:学习曲线平缓,易于上手
- 强大的功能:支持方法调用验证、返回值设定、异常抛出等
- 良好的可读性:测试代码直观易懂
- 活跃的社区:持续更新维护,文档完善
Mockito 核心概念
1. 创建 Mock 对象
实例
// 方式1:使用静态方法
List mockedList = Mockito.mock(List.class);
// 方式2:使用注解(需要搭配 MockitoJUnitRunner)
@Mock
List mockedList;
List mockedList = Mockito.mock(List.class);
// 方式2:使用注解(需要搭配 MockitoJUnitRunner)
@Mock
List mockedList;
2. 设定方法行为
实例
// 当调用 mockedList.size() 时返回 100
when(mockedList.size()).thenReturn(100);
// 当调用 mockedList.get(0) 时返回 "first"
when(mockedList.get(0)).thenReturn("first");
// 当调用 mockedList.get(1) 时抛出异常
when(mockedList.get(1)).thenThrow(new RuntimeException());
when(mockedList.size()).thenReturn(100);
// 当调用 mockedList.get(0) 时返回 "first"
when(mockedList.get(0)).thenReturn("first");
// 当调用 mockedList.get(1) 时抛出异常
when(mockedList.get(1)).thenThrow(new RuntimeException());
3. 验证交互
实例
// 验证 mockedList.add("one") 被调用了一次
verify(mockedList).add("one");
// 验证 mockedList.clear() 从未被调用
verify(mockedList, never()).clear();
// 验证 mockedList.add("two") 被调用了至少两次
verify(mockedList, atLeast(2)).add("two");
verify(mockedList).add("one");
// 验证 mockedList.clear() 从未被调用
verify(mockedList, never()).clear();
// 验证 mockedList.add("two") 被调用了至少两次
verify(mockedList, atLeast(2)).add("two");
Mockito 实际应用示例
测试用户服务
假设我们有一个 UserService
依赖于 UserRepository
:
实例
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
}
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
}
使用 Mockito 进行测试:
实例
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testGetUserById_Success() {
// 准备测试数据
User mockUser = new User(1L, "testUser");
// 设定 Mock 行为
when(userRepository.findById(1L))
.thenReturn(Optional.of(mockUser));
// 执行测试
User result = userService.getUserById(1L);
// 验证结果
assertEquals("testUser", result.getUsername());
verify(userRepository).findById(1L);
}
@Test(expected = UserNotFoundException.class)
public void testGetUserById_NotFound() {
when(userRepository.findById(2L))
.thenReturn(Optional.empty());
userService.getUserById(2L);
}
}
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testGetUserById_Success() {
// 准备测试数据
User mockUser = new User(1L, "testUser");
// 设定 Mock 行为
when(userRepository.findById(1L))
.thenReturn(Optional.of(mockUser));
// 执行测试
User result = userService.getUserById(1L);
// 验证结果
assertEquals("testUser", result.getUsername());
verify(userRepository).findById(1L);
}
@Test(expected = UserNotFoundException.class)
public void testGetUserById_NotFound() {
when(userRepository.findById(2L))
.thenReturn(Optional.empty());
userService.getUserById(2L);
}
}
Mockito 高级特性
1. 参数匹配器
实例
// 任何整数参数
when(mockedList.get(anyInt())).thenReturn("element");
// 特定类型的参数
when(mockedList.contains(anyString())).thenReturn(true);
// 自定义匹配器
when(mockedList.add(argThat(arg -> arg.length() > 5))).thenReturn(true);
when(mockedList.get(anyInt())).thenReturn("element");
// 特定类型的参数
when(mockedList.contains(anyString())).thenReturn(true);
// 自定义匹配器
when(mockedList.add(argThat(arg -> arg.length() > 5))).thenReturn(true);
2. 验证调用顺序
实例
InOrder inOrder = inOrder(mockedList);
inOrder.verify(mockedList).add("first");
inOrder.verify(mockedList).add("second");
inOrder.verify(mockedList).add("first");
inOrder.verify(mockedList).add("second");
3. 部分 Mock (Spy)
实例
List realList = new ArrayList();
List spyList = spy(realList);
// 调用真实方法
spyList.add("real");
// 模拟特定方法
doReturn(100).when(spyList).size();
List spyList = spy(realList);
// 调用真实方法
spyList.add("real");
// 模拟特定方法
doReturn(100).when(spyList).size();
最佳实践
- 不要过度使用 Mock:只 Mock 必要的依赖,保持测试的真实性
- 验证交互要适度:关注重要的交互,不要验证每个方法调用
- 保持测试简洁:每个测试方法应该只测试一个功能点
- 合理组织测试代码:使用 @Before 进行公共设置
- 结合其他测试工具:与 JUnit、AssertJ 等配合使用
常见问题解答
Q: Mockito 和 PowerMock 有什么区别?
A: Mockito 主要用于普通对象的 Mock,而 PowerMock 可以 Mock 静态方法、构造函数等。但 PowerMock 破坏了测试的隔离性,建议优先使用 Mockito。
Q: 什么时候应该使用 Spy 而不是 Mock?
A: 当你需要大部分真实行为,只修改少数方法时使用 Spy。如果需要完全控制对象行为,则使用 Mock。
Q: Mockito 能否用于 final 类或方法?
A: 从 Mockito 2.1.0 开始,通过配置可以 Mock final 类和方法,但需要添加 mockito-inline
依赖。
点我分享笔记