一、Mockito 概述与核心概念
1.1 什么是 Mockito?
Mockito 是 Java 生态中最流行的模拟测试框架,用于创建和管理测试替身(Test Doubles)。它通过简洁的 API 帮助开发者编写干净且可维护的单元测试,特别适用于测试驱动开发(TDD)和行为驱动开发(BDD)场景。Mockito 最初由 Szczepan Faber 创建,现已成为 Java 开发者进行单元测试的事实标准工具之一。
Mockito 的核心价值:
- 隔离测试:将被测代码与外部依赖隔离
- 行为验证:验证对象间的交互是否符合预期
- 简化测试:减少样板代码,使测试更专注
- 灵活配置:支持多种模拟场景和复杂行为
- 错误诊断:提供清晰的失败信息,便于调试
1.2 Mockito 核心概念
概念 | 描述 | 对应类/方法 |
---|---|---|
Mock 对象 | 模拟真实对象的替身 | mock() |
Spy 对象 | 部分真实部分模拟的对象 | spy() |
Stubbing | 定义模拟对象的行为 | when().thenReturn() |
Verification | 验证交互行为 | verify() |
Argument Matchers | 参数匹配器 | any() , eq() |
BDD 风格 | Given-When-Then 结构 | given().willReturn() |
1.3 Mockito 版本演进
版本 | 发布时间 | 主要特性 |
---|---|---|
1.x | 2008 | 基础模拟功能 |
2.x | 2016 | Java 8 支持,改进 API |
3.x | 2019 | 支持 JUnit 5,模块化架构 |
4.x | 2021 | 改进 mock() API,支持 mock 构造器 |
二、Mockito 快速入门
2.1 环境配置
2.1.1 Maven 依赖
xml
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<!-- 与JUnit5集成 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
2.1.2 基本使用示例
java
import static org.mockito.Mockito.*;
// 创建mock对象
List<String> mockedList = mock(List.class);
// 设置stubbing
when(mockedList.get(0)).thenReturn("first");
// 使用mock对象
String result = mockedList.get(0); // 返回 "first"
// 验证交互
verify(mockedList).get(0);
2.2 与 JUnit 集成
2.2.1 JUnit 4 集成
java
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
public void testGetUser() {
// 测试代码
}
}
2.2.2 JUnit 5 集成
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void testGetUser() {
// 测试代码
}
}
三、Mockito 核心功能详解
3.1 创建 Mock 对象
3.1.1 基本 Mock 创建
java
// 方式1:静态方法
List<String> listMock = mock(List.class);
// 方式2:注解
@Mock
List<String> listMock;
// 带自定义配置的mock
List<String> listMock = mock(List.class, withSettings()
.name("namedListMock")
.defaultAnswer(RETURNS_SMART_NULLS));
3.1.2 不同 Mock 策略
策略 | 描述 | 适用场景 |
---|---|---|
RETURNS_DEFAULTS |
默认策略,返回0/null/false | 一般情况 |
RETURNS_SMART_NULLS |
返回SmartNull而非null | 调试NPE问题 |
RETURNS_MOCKS |
返回其他mock对象 | 复杂对象图 |
RETURNS_DEEP_STUBS |
支持链式调用 | Builder模式 |
CALLS_REAL_METHODS |
调用真实方法 | 部分模拟 |
3.2 Stubbing 行为配置
3.2.1 基本 Stubbing
java
// 返回值stubbing
when(mockList.get(0)).thenReturn("first");
// 异常stubbing
when(mockList.get(1)).thenThrow(new RuntimeException());
// 连续stubbing
when(mockList.size())
.thenReturn(1)
.thenReturn(2)
.thenThrow(new RuntimeException());
3.2.2 参数匹配器
java
// 使用内置匹配器
when(mockList.get(anyInt())).thenReturn("element");
// 多个参数匹配
Map<String, String> mockMap = mock(Map.class);
when(mockMap.put(anyString(), contains("val"))).thenReturn("oldValue");
// 自定义匹配器
when(mockList.add(argThat(s -> s.length() > 5))).thenReturn(true);
常用参数匹配器:
any()
,anyInt()
,anyString()
- 匹配任意值eq()
- 严格匹配isNull()
,isNotNull()
- 空值检查contains()
,endsWith()
- 字符串匹配argThat()
- 自定义匹配逻辑
3.3 验证交互行为
3.3.1 基本验证
java
// 验证方法调用
verify(mockList).add("one");
// 验证调用次数
verify(mockList, times(1)).add("one");
verify(mockList, atLeastOnce()).add("one");
verify(mockList, atMost(5)).add("one");
verify(mockList, never()).clear();
// 验证调用顺序
InOrder inOrder = inOrder(mockList);
inOrder.verify(mockList).add("one");
inOrder.verify(mockList).add("two");
3.3.2 高级验证
java
// 验证无其他交互
verifyNoMoreInteractions(mockList);
// 验证零交互
verifyZeroInteractions(mockList);
// 超时验证
verify(mockList, timeout(100)).clear();
// 捕获参数
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mockList).add(captor.capture());
assertEquals("one", captor.getValue());
四、Mockito 高级特性
4.1 Spy 对象(部分模拟)
java
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList);
// 部分方法使用真实实现
doReturn(100).when(spyList).size();
spyList.add("one");
assertEquals(100, spyList.size());
assertEquals(1, realList.size());
verify(spyList).add("one");
Spy 与 Mock 的区别:
- Mock:所有方法默认被模拟,除非显式配置
- Spy:所有方法默认调用真实实现,除非显式配置
4.2 BDD 风格测试
java
import static org.mockito.BDDMockito.*;
@Test
void getUserById_ShouldReturnUser_WhenUserExists() {
// Given
given(userRepository.findById(1L))
.willReturn(Optional.of(new User(1L, "test")));
// When
User user = userService.getUserById(1L);
// Then
then(userRepository).should().findById(1L);
assertEquals("test", user.getUsername());
}
4.3 模拟静态方法(Mockito 3.4+)
java
try (MockedStatic<UtilityClass> mockedStatic = mockStatic(UtilityClass.class)) {
// 配置静态方法
mockedStatic.when(UtilityClass::staticMethod).thenReturn("mocked");
// 执行测试
String result = UtilityClass.staticMethod();
// 验证
assertEquals("mocked", result);
mockedStatic.verify(UtilityClass::staticMethod);
}
4.4 模拟构造器(Mockito 3.5+)
java
try (MockedConstruction<User> mockedConstruction = mockConstruction(User.class)) {
// 创建实例时会返回mock对象
User user = new User();
// 配置mock行为
when(user.getId()).thenReturn(1L);
// 使用mock对象
assertEquals(1L, user.getId());
// 验证构造调用
assertEquals(1, mockedConstruction.constructed().size());
}
五、Mockito 最佳实践
5.1 测试设计原则
- 单一职责:每个测试只验证一个行为
- 明确命名:测试名应表达场景和预期
- 3A模式:Arrange-Act-Assert 结构
- 最小Stubbing:只配置必要的模拟行为
- 验证必要交互:避免过度验证实现细节
5.2 常见陷阱与解决方案
问题 | 原因 | 解决方案 |
---|---|---|
UnnecessaryStubbingException |
配置了未使用的stubbing | 检查测试逻辑,移除无用stubbing |
ArgumentCaptor 捕获不到参数 |
验证的方法未被调用 | 确认方法确实被调用 |
Mock 返回意外值 | 参数匹配不精确 | 使用更严格的匹配器 |
NPE 异常 | 未配置返回值 | 使用 RETURNS_SMART_NULLS |
验证失败 | 调用次数不匹配 | 检查业务逻辑和验证条件 |
5.3 性能优化
- 复用 Mock 对象 :在
@Before
中初始化 - 轻量级测试:避免复杂对象图
- 合理使用 Spy:优先使用 Mock
- 限制验证范围:避免不必要的严格验证
- 使用静态导入:提高代码可读性
六、Mockito 与 Spring 集成
6.1 Spring Boot 测试支持
java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void getUser_ShouldReturnUser() throws Exception {
given(userService.getUser(1L))
.willReturn(new User(1L, "test"));
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("test"));
}
}
6.2 分层测试策略
测试类型 | 使用 Mockito 的方式 | Spring 测试注解 |
---|---|---|
单元测试 | 直接 Mock 依赖 | 无 |
组件测试 | @MockBean |
@WebMvcTest , @DataJpaTest |
集成测试 | 部分 Mock | @SpringBootTest |
七、高级测试场景
7.1 异步代码测试
java
@Test
void testAsyncOperation() {
CompletableFuture<User> future = new CompletableFuture<>();
when(userService.getUserAsync(1L)).thenReturn(future);
// 触发异步调用
CompletableFuture<User> result = userController.getUserAsync(1L);
// 完成异步操作
future.complete(new User(1L, "test"));
// 验证结果
assertEquals("test", result.get().getUsername());
}
7.2 并发测试
java
@Test
void testConcurrentAccess() {
UserRepository mockRepo = mock(UserRepository.class);
when(mockRepo.count()).thenAnswer(invocation -> {
Thread.sleep(100); // 模拟延迟
return 1L;
});
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<Long>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
futures.add(executor.submit(() -> mockRepo.count()));
}
// 验证所有调用都成功
for (Future<Long> future : futures) {
assertEquals(1L, future.get());
}
verify(mockRepo, times(10)).count();
}
7.3 自定义 Answer 实现
java
when(mockList.get(anyInt())).thenAnswer(invocation -> {
int index = invocation.getArgument(0);
return "element_" + index;
});
assertEquals("element_1", mockList.get(1));
八、Mockito 扩展生态
8.1 Mockito 与 Kotlin
kotlin
class UserServiceTest {
@MockK
lateinit var userRepository: UserRepository
@InjectMockKs
lateinit var userService: UserService
@BeforeEach
fun setUp() = MockitoAnnotations.openMocks(this)
@Test
fun `getUser should return user`() {
every { userRepository.findById(1) } returns Optional.of(User(1, "test"))
val user = userService.getUser(1)
assertEquals("test", user.username)
verify { userRepository.findById(1) }
}
}
8.2 PowerMock 集成
java
@RunWith(PowerMockRunner.class)
@PrepareForTest(UtilityClass.class)
public class PowerMockTest {
@Test
public void testStaticMethod() {
// 模拟静态方法
mockStatic(UtilityClass.class);
when(UtilityClass.staticMethod()).thenReturn("mocked");
assertEquals("mocked", UtilityClass.staticMethod());
}
}
8.3 Mockito 与 Testcontainers
java
@Testcontainers
@SpringBootTest
class IntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13");
@MockBean
private ExternalService externalService;
@Test
void testWithRealDatabaseAndMockedService() {
when(externalService.call()).thenReturn("mocked");
// 测试代码
}
}
九、Mockito 内部原理
9.1 代理机制
Mockito 使用动态代理创建 Mock 对象:
- 对于接口:使用 JDK 动态代理
- 对于类:使用 Byte Buddy 生成子类
9.2 Mock 处理流程
- 调用拦截:Mock 对象拦截所有方法调用
- Stubbing 匹配:检查是否有匹配的 stubbing
- 默认行为:无 stubbing 时执行默认 Answer
- 调用记录:记录交互信息用于验证
9.3 验证机制
Mockito 维护了调用记录器(InvocationContainer),在验证时:
- 查找匹配的调用记录
- 检查调用次数是否符合预期
- 生成详细的错误信息(如有失败)
十、总结
Mockito 作为 Java 测试生态的核心工具,通过其简洁而强大的 API 极大简化了单元测试的编写。本文全面介绍了:
- Mockito 的核心概念与基础用法
- 高级特性如静态方法模拟、构造器模拟
- 与 Spring、Kotlin 等技术的集成
- 复杂场景如异步、并发测试的实现
- 最佳实践与性能优化建议
掌握 Mockito 不仅能提高测试代码的质量和可维护性,还能促进更好的软件设计。希望本指南能成为你测试工具箱中的宝贵资源,助力构建更加健壮可靠的系统。
PS:如果你在学习过程中遇到问题,别担心!欢迎在评论区留言,我会尽力帮你解决!😄