Mockito单元测试
%% Mockito核心流程示意图
flowchart TD
A[数据准备] -->|创建真实对象/模拟对象| B[行为打桩]
B -->|定义方法返回值/异常| C[执行测试]
C -->|调用被测方法| D[结果验证]
D -->|断言/调用验证| E[测试报告]
一、单元测试核心四要素
1.1 完整测试流程解析
sequenceDiagram
participant T as 测试用例
participant M as Mock对象
participant S as 被测系统
T->>M: 数据准备(创建模拟对象)
T->>M: 行为打桩(定义方法返回值)
T->>S: 执行被测方法
S->>M: 调用依赖方法
M-->>S: 返回预设值
T->>M: 验证方法调用
T->>T: 断言结果校验
1.2 核心概念详解
- 数据准备:创建待测对象及其依赖的模拟对象(真实对象或Mock对象)
- 打桩(Stubbing):定义模拟对象的方法调用规则(返回值/异常/延时等)
- 执行测试:触发被测对象的业务方法执行
- 验证(Verification):验证方法调用次数、参数值和执行顺序
二、环境搭建与配置
2.1 Maven依赖配置
<!-- pom.xml -->
<dependencies>
<!-- 测试相关依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito核心库 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>
2.2 测试类基础模板
@RunWith(MockitoJUnitRunner.class)
public class OrderServiceTest {
@Mock
private PaymentDao paymentDao; // MyBatis Mapper接口
@Spy
private InventoryService inventoryService;
@InjectMocks // @InjectMocks 是 Mockito 框架中用于依赖自动注入的注解,它会将被 @Mock 或 @Spy 修饰的模拟对象自动注入到被测试类的实例中。
private OrderService orderService;
@Before
public void setUp() {
// MockitoAnnotations.initMocks(this); // 与 @RunWith(MockitoJUnitRunner.class) 的二选一
}
}
三、基础使用示例
3.1 MyBatis Mapper模拟
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
@Mock
private UserMapper userMapper;
@InjectMocks
private UserService userService;
@Test
public void testGetUserById() {
// 准备测试数据
User mockUser = new User(1, "测试用户", "test@example.com");
// 打桩:当调用selectById(1)时返回模拟用户
when(userMapper.selectById(1)).thenReturn(mockUser);
// 执行测试
UserDTO result = userService.getUserDetail(1);
// 验证结果
assertEquals("邮箱匹配", "test@example.com", result.getEmail());
// 验证Mapper调用
verify(userMapper, times(1)).selectById(1); // 验证userMapper.selectById是否被调用了1次
}
}
3.2 HTTP接口模拟
@RunWith(MockitoJUnitRunner.class)
public class WeatherServiceTest {
@Mock
private RestTemplate restTemplate;
@InjectMocks
private WeatherService weatherService;
@Test
public void testGetWeatherInfo() {
// 准备模拟响应
String jsonResponse = "{'temperature':25,'humidity':60}";
when(restTemplate.getForObject(anyString(), eq(String.class)))
.thenReturn(jsonResponse);
// 执行测试
WeatherDTO weather = weatherService.getCurrentWeather("101010100");
// 断言验证
assertNotNull("返回值不为空", weather);
assertEquals("温度正确", 25, weather.getTemperature());
// 验证接口调用
verify(restTemplate).getForObject(
contains("101010100"),
eq(String.class)
);
}
}
四、核心功能深度解析
4.1 Mock与Spy对比
classDiagram
class MockObject {
+所有方法返回默认值
+需显式定义行为
+不执行真实方法
}
class SpyObject {
+调用真实方法
+可部分覆盖行为
+保留对象状态
}
MockObject <|-- SpyObject : 扩展关系
差异点总结:
- Mock对象:完全虚拟对象,所有方法需显式定义
- Spy对象:部分真实对象,仅覆盖需要mock的方法
- 内存占用:Mock对象更轻量,Spy需要维护真实对象状态
- 执行速度:Mock对象更快,Spy涉及真实方法调用
4.2 高级打桩技巧
// 连续不同返回值
when(mockDao.getCount())
.thenReturn(10) // 第一次调用返回
.thenReturn(20) // 第二次调用返回
.thenThrow(new RuntimeException()); // 第三次调用返回
// 使用 `argThat` 动态匹配参数,只返回符合条件的模拟数据
when(userDao.findByCriteria(
argThat(c -> c.getAge() > 18) // 使用 Lambda 表达式定义匹配规则:仅当 age > 18 时匹配
)).thenReturn(adultUsers); // 返回符合条件的模拟数据(adultUsers)
// 使用 `thenAnswer` 动态生成返回值,可以访问调用参数并返回自定义响应
when(httpClient.execute(any())) // 匹配任意 HttpRequest
.thenAnswer(invocation -> { // 使用 `invocation` 获取调用参数
HttpRequest request = invocation.getArgument(0); // 获取第 1 个参数(HttpRequest)
return buildResponse(request.getUrl()); // 根据 URL 动态生成响应
});
五、SpringBoot集成测试
5.1 服务层集成测试
@RunWith(SpringRunner.class) // 需要Spring容器
@SpringBootTest
public class ProductServiceIntegrationTest {
@MockBean // 替换Spring容器中的真实 Bean
private InventoryMapper inventoryMapper;
@Autowired
private ProductService productService;
@Test
public void testCheckInventory() {
// 打桩数据库操作
when(inventoryMapper.selectStock(1001L))
.thenReturn(5)
.thenReturn(0);
// 第一次调用
assertTrue(productService.isAvailable(1001L));
// 第二次调用
assertFalse(productService.isAvailable(1001L));
// 验证调用次数
verify(inventoryMapper, times(2)).selectStock(1001L); // 验证inventoryMapper.selectStock()是否被调用了两次
}
}
5.2 Controller层测试
@WebMvcTest(OrderController.class)
public class OrderControllerTest {
@MockBean // 替换Spring容器中的真实 Bean
private OrderService orderService;
@Autowired
private MockMvc mockMvc;
@Test
public void testCreateOrder() throws Exception {
// 准备请求数据
String jsonBody = "{'productId':1001,'quantity':2}";
// 打桩服务方法
when(orderService.createOrder(any()))
.thenReturn("ORDER_20230801001");
// 执行HTTP请求
mockMvc.perform(post("/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonBody))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.orderNo").exists());
// 验证服务调用
verify(orderService).createOrder(argThat(order ->
order.getProductId() == 1001 && order.getQuantity() == 2));
}
}
六、MyBatis专项Mock
6.1 Mapper接口模拟
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
@Mock
private UserMapper userMapper;
@InjectMocks
private UserService userService;
@Test
public void testBatchInsert() {
// 准备测试数据
List<User> users = Arrays.asList(
new User("张三"),
new User("李四")
);
// 打桩插入操作
when(userMapper.batchInsert(anyList())).thenReturn(2);
// 执行批量插入
int result = userService.batchCreateUsers(users);
// 验证结果
assertEquals("插入数量正确", 2, result);
// 验证参数传递
verify(userMapper).batchInsert(argThat(list ->
list.size() == 2 &&
list.get(0).getName().equals("张三")
));
}
}
6.2 动态SQL验证
@Test
public void testDynamicQuery() {
// 准备查询条件
UserQuery query = new UserQuery();
query.setActive(true);
query.setMinAge(18);
// 打桩Mapper方法
when(userMapper.searchUsers(argThat(q ->
q.isActive() && q.getMinAge() == 18
))).thenReturn(adultUsers);
// 执行查询
List<User> result = userService.searchUsers(query);
// 验证动态SQL生成
verify(userMapper).searchUsers(argThat(q ->
q.getQuerySql().contains("WHERE status = 1")
));
}
七、常见问题解决方案
7.1 NPE问题处理
问题场景:未初始化Mock对象导致空指针
// 错误示例
@Mock
private UserDao userDao; // 未初始化
// 正确解决
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
7.2 参数验证失败
问题场景:实际参数与预期不匹配
// 精确验证
verify(userDao).updateStatus(
eq(1001L), // 用户ID
eq("ACTIVE"), // 新状态
isNotNull() // 修改时间
);
7.3 静态方法Mock
// 使用Mockito-inline
try (MockedStatic<DateUtils> utilities = Mockito.mockStatic(DateUtils.class)) {
utilities.when(DateUtils::getCurrentDate)
.thenReturn(fixedDate);
// 执行测试逻辑...
}
版权声明:凡未经本网站书面授权,任何媒体、网站及个人不得转载、复制、重制、改动、展示或使用本网站的局部或全部的内容或服务,或在非本网站所属服务器上建立镜像。如果已转载,请自行删除。同时,我们保留进一步追究相关行为主体的法律责任的权利。我们希望与各媒体合作,签订著作权有偿使用许可合同,故转载方须书面/邮件申请,以待商榷。