Mockito:单元测试框架实战
一、初识 Mockito
Mockito 是 Java 生态中最流行的 Mock 框架,被广泛应用于单元测试中。它提供了简洁优雅的 API,帮助开发者轻松创建和配置 Mock 对象,从而实现对依赖项的隔离测试。
为什么需要 Mock?
在实际开发中,我们常常遇到以下场景:
✓ 依赖的外部服务(数据库、HTTP API)难以在测试环境搭建
✓ 某些依赖执行缓慢,影响测试效率
✓ 需要模拟各种异常情况进行测试
✓ 希望独立测试某个模块,不受其他模块影响
Mockito 正是为解决这些问题而生,它可以创建虚拟对象替代真实依赖,让测试变得简单高效。
Mockito 的核心优势
简洁的 API - 使用流畅的链式调用,代码可读性强
强大的验证功能 - 可以验证方法调用次数、参数等
灵活的参数匹配 - 支持精确匹配、类型匹配、自定义匹配
注解支持 - 通过注解简化 Mock 对象的创建
与主流框架集成 - 完美支持 JUnit、Spring Boot 等
二、Mockito 核心架构

Mockito 的架构分为四个主要层次:
- 测试代码层 - 我们编写的测试代码
- Mockito API - 提供 mock()、when()、verify() 等核心方法
- Mock 对象代理 - 动态创建的代理对象
- 字节码增强 - 底层使用 ByteBuddy 进行字节码操作
三、快速开始
添加依赖
在项目中添加 Mockito 依赖
<!-- Maven -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<!-- 如果使用 JUnit 5 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
基本用法三步曲
步骤 一:创建 Mock 对象
import static org.mockito.Mockito.*;
// 方式一:使用 mock() 方法
List<String> mockList = mock(List.class);
// 方式二:使用 @Mock 注解
@Mock
private UserRepository userRepository;
步骤二:定义 Mock 行为
// 定义返回值
when(mockList.get(0)).thenReturn("first");
when(mockList.size()).thenReturn(10);
// 定义异常
when(mockList.get(1))
.thenThrow(new RuntimeException("出错了"));
// 链式调用
when(mockList.isEmpty())
.thenReturn(false)
.thenReturn(true); // 第二次调用返回 true
步骤三:验证方法调用
// 验证方法被调用
verify(mockList).add("item");
// 验证调用次数
verify(mockList, times(2)).add(anyString());
// 验证从未调用
verify(mockList, never()).clear();
四、验证方法详解

Mockito 提供了丰富的验证方法来检查 Mock 对象的交互:
调用次数验证
import static org.mockito.Mockito.*;
@Test
public void testVerification() {
List<String> mockList = mock(List.class);
mockList.add("once");
mockList.add("twice");
mockList.add("twice");
// 验证调用 1 次(默认)
verify(mockList).add("once");
// 验证调用 2 次
verify(mockList, times(2)).add("twice");
// 验证从未调用
verify(mockList, never()).clear();
// 验证至少调用 1 次
verify(mockList, atLeastOnce()).add(anyString());
// 验证至少调用 2 次
verify(mockList, atLeast(2)).add("twice");
// 验证最多调用 5 次
verify(mockList, atMost(5)).add(anyString());
}
调用顺序验证
@Test
public void testInOrder() {
List<String> firstMock = mock(List.class);
List<String> secondMock = mock(List.class);
firstMock.add("first");
secondMock.add("second");
firstMock.add("third");
// 验证调用顺序
InOrder inOrder = inOrder(firstMock, secondMock);
inOrder.verify(firstMock).add("first");
inOrder.verify(secondMock).add("second");
inOrder.verify(firstMock).add("third");
}
超时验证
@Test
public void testTimeout() {
List<String> mockList = mock(List.class);
// 异步调用
new Thread(() -> {
try {
Thread.sleep(100);
mockList.add("async");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 验证在 200ms 内被调用
verify(mockList, timeout(200)).add("async");
}
五、参数匹配器

参数匹配器(ArgumentMatchers)让我们可以灵活地匹配方法参数。
常用匹配器
import static org.mockito.ArgumentMatchers.*;
@Test
public void testMatchers() {
Map<String, String> mockMap = mock(Map.class);
// any() - 匹配任何对象
when(mockMap.get(any())).thenReturn("value");
// anyString() - 匹配任何字符串
when(mockMap.put(anyString(), anyString()))
.thenReturn("old");
// anyInt() - 匹配任何整数
when(mockMap.remove(anyInt())).thenReturn("removed");
// eq() - 精确匹配
when(mockMap.get(eq("key")))
.thenReturn("specific");
// isNull() - 匹配 null
when(mockMap.get(isNull()))
.thenReturn("null-value");
// isNotNull() - 匹配非 null
when(mockMap.containsKey(isNotNull()))
.thenReturn(true);
}
自定义匹配器
@Test
public void testCustomMatcher() {
List<String> mockList = mock(List.class);
// 使用 argThat 自定义匹配逻辑
when(mockList.add(argThat(s -> s.length() > 5)))
.thenReturn(true);
assertTrue(mockList.add("long string")); // true
assertFalse(mockList.add("short")); // false
}
混合使用注意事项
// 错误:不能混用匹配器和具体值
when(mockMap.put(anyString(), "value"))
.thenReturn("old");
// 正确:全部使用匹配器
when(mockMap.put(anyString(), eq("value")))
.thenReturn("old");
六、高级特性

使用注解简化代码
Mockito 提供了多个注解来简化 Mock 对象的创建和注入:
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
// @Mock 创建 Mock 对象
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
// @InjectMocks 自动注入 Mock 依赖
@InjectMocks
private UserService userService;
@Test
public void testCreateUser() {
User user = new User("张三", "zhang@example.com");
when(userRepository.save(any(User.class)))
.thenReturn(user);
User created = userService.createUser(user);
verify(userRepository).save(user);
verify(emailService)
.sendWelcomeEmail(user.getEmail());
assertEquals("张三", created.getName());
}
}
Spy - 部分 Mock
Spy 允许我们监视真实对象,只 Mock 部分方法:
@Test
public void testSpy() {
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList);
// 真实方法会被调用
spyList.add("one");
spyList.add("two");
// Mock 特定方法
when(spyList.size()).thenReturn(100);
assertEquals(100, spyList.size()); // Mock 的行为
assertEquals("one", spyList.get(0)); // 真实的行为
}
实际应用场景:
public class OrderService {
public double calculateTotal(Order order) {
double subtotal = calculateSubtotal(order);
double tax = calculateTax(subtotal);
return subtotal + tax;
}
protected double calculateSubtotal(Order order) {
return order.getItems().stream()
.mapToDouble(Item::getPrice)
.sum();
}
protected double calculateTax(double amount) {
return amount * 0.1;
}
}
@Test
public void testCalculateTotal() {
OrderService orderService = spy(new OrderService());
// 只 Mock calculateTax 方法
doReturn(50.0)
.when(orderService)
.calculateTax(anyDouble());
Order order = new Order();
order.addItem(new Item("商品", 100.0));
double total = orderService.calculateTotal(order);
assertEquals(150.0, total); // 100 + 50
}
Answer - 自定义返回逻辑
Answer 接口允许我们实现复杂的返回逻辑:
@Test
public void testAnswer() {
List<String> mockList = mock(List.class);
// 根据参数动态返回结果
when(mockList.get(anyInt())).thenAnswer(invocation -> {
Integer index = invocation.getArgument(0);
return "element-" + index;
});
assertEquals("element-0", mockList.get(0));
assertEquals("element-5", mockList.get(5));
}
实际应用场景 - 模拟异步回调:
public interface AsyncService {
void executeAsync(String task, Callback callback);
}
@Test
public void testAsyncCallback() {
AsyncService asyncService = mock(AsyncService.class);
doAnswer(invocation -> {
String task = invocation.getArgument(0);
Callback callback = invocation.getArgument(1);
// 模拟异步执行后调用回调
callback.onSuccess("完成: " + task);
return null;
}).when(asyncService)
.executeAsync(anyString(), any(Callback.class));
Callback mockCallback = mock(Callback.class);
asyncService.executeAsync("任务1", mockCallback);
verify(mockCallback).onSuccess("完成: 任务1");
}
ArgumentCaptor - 捕获参数
ArgumentCaptor 用于捕获传递给 Mock 方法的参数,便于后续断言:
@Test
public void testArgumentCaptor() {
List<String> mockList = mock(List.class);
mockList.add("John");
mockList.add("Jane");
// 创建 ArgumentCaptor
ArgumentCaptor<String> captor =
ArgumentCaptor.forClass(String.class);
// 捕获所有调用的参数
verify(mockList, times(2)).add(captor.capture());
List<String> capturedValues = captor.getAllValues();
assertEquals(
Arrays.asList("John", "Jane"),
capturedValues
);
}
实际应用场景 - 验证复杂对象:
@Test
public void testCaptureComplexObject() {
UserRepository userRepository =
mock(UserRepository.class);
UserService userService =
new UserService(userRepository);
userService.registerUser(
"张三",
"zhang@example.com",
25
);
// 捕获传递给 save 方法的 User 对象
ArgumentCaptor<User> userCaptor =
ArgumentCaptor.forClass(User.class);
verify(userRepository).save(userCaptor.capture());
User capturedUser = userCaptor.getValue();
assertEquals("张三", capturedUser.getName());
assertEquals(
"zhang@example.com",
capturedUser.getEmail()
);
assertEquals(25, capturedUser.getAge());
assertNotNull(capturedUser.getCreatedAt());
}
七、Spring Boot 集成实战
在实际项目中,Mockito 最常见的应用场景是测试 Spring Boot 应用。
依赖配置
Spring Boot 项目通常已经包含 Mockito,但建议显式添加:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Service 层测试
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
private final PaymentService paymentService;
private final NotificationService notificationService;
@Autowired
public OrderService(
OrderRepository orderRepository,
InventoryService inventoryService,
PaymentService paymentService,
NotificationService notificationService
) {
this.orderRepository = orderRepository;
this.inventoryService = inventoryService;
this.paymentService = paymentService;
this.notificationService = notificationService;
}
public Order createOrder(OrderRequest request) {
// 1. 检查库存
if (!inventoryService.checkStock(
request.getProductId(),
request.getQuantity()
)) {
throw new InsufficientStockException("库存不足");
}
// 2. 创建订单
Order order = new Order();
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setUserId(request.getUserId());
order.setStatus(OrderStatus.PENDING);
order.setCreatedAt(LocalDateTime.now());
Order savedOrder = orderRepository.save(order);
// 3. 扣减库存
inventoryService.decreaseStock(
request.getProductId(),
request.getQuantity()
);
// 4. 处理支付
PaymentResult paymentResult =
paymentService.processPayment(
savedOrder.getId(),
request.getPaymentMethod(),
calculateAmount(request)
);
if (paymentResult.isSuccess()) {
savedOrder.setStatus(OrderStatus.PAID);
orderRepository.save(savedOrder);
// 5. 发送通知
notificationService
.sendOrderConfirmation(savedOrder);
} else {
// 回滚库存
inventoryService.increaseStock(
request.getProductId(),
request.getQuantity()
);
savedOrder.setStatus(OrderStatus.FAILED);
}
return savedOrder;
}
private BigDecimal calculateAmount(OrderRequest request) {
return BigDecimal.valueOf(
request.getQuantity() * 100
);
}
}
测试类:
@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
@Mock
private InventoryService inventoryService;
@Mock
private PaymentService paymentService;
@Mock
private NotificationService notificationService;
@InjectMocks
private OrderService orderService;
private OrderRequest orderRequest;
@BeforeEach
void setUp() {
orderRequest = new OrderRequest();
orderRequest.setProductId(1L);
orderRequest.setQuantity(2);
orderRequest.setUserId(100L);
orderRequest.setPaymentMethod("CREDIT_CARD");
}
@Test
@DisplayName("成功创建订单")
void testCreateOrder_Success() {
// Given
when(inventoryService.checkStock(1L, 2))
.thenReturn(true);
Order mockOrder = new Order();
mockOrder.setId(1L);
mockOrder.setStatus(OrderStatus.PENDING);
when(orderRepository.save(any(Order.class)))
.thenReturn(mockOrder);
PaymentResult paymentResult =
new PaymentResult(true, "SUCCESS");
when(paymentService.processPayment(
anyLong(),
anyString(),
any(BigDecimal.class)
)).thenReturn(paymentResult);
// When
Order result = orderService.createOrder(orderRequest);
// Then
assertNotNull(result);
assertEquals(OrderStatus.PAID, result.getStatus());
// 验证交互
verify(inventoryService).checkStock(1L, 2);
verify(inventoryService).decreaseStock(1L, 2);
verify(orderRepository, times(2))
.save(any(Order.class));
verify(paymentService).processPayment(
eq(1L),
eq("CREDIT_CARD"),
any(BigDecimal.class)
);
verify(notificationService)
.sendOrderConfirmation(result);
}
@Test
@DisplayName("库存不足时抛出异常")
void testCreateOrder_InsufficientStock() {
// Given
when(inventoryService.checkStock(1L, 2))
.thenReturn(false);
// When & Then
assertThrows(
InsufficientStockException.class,
() -> orderService.createOrder(orderRequest)
);
// 验证不会调用后续方法
verify(inventoryService).checkStock(1L, 2);
verify(inventoryService, never())
.decreaseStock(anyLong(), anyInt());
verify(orderRepository, never())
.save(any(Order.class));
verify(paymentService, never()).processPayment(
anyLong(),
anyString(),
any(BigDecimal.class)
);
}
@Test
@DisplayName("支付失败时回滚库存")
void testCreateOrder_PaymentFailed() {
// Given
when(inventoryService.checkStock(1L, 2))
.thenReturn(true);
Order mockOrder = new Order();
mockOrder.setId(1L);
when(orderRepository.save(any(Order.class)))
.thenReturn(mockOrder);
PaymentResult paymentResult =
new PaymentResult(false, "INSUFFICIENT_FUNDS");
when(paymentService.processPayment(
anyLong(),
anyString(),
any(BigDecimal.class)
)).thenReturn(paymentResult);
// When
Order result = orderService.createOrder(orderRequest);
// Then
assertEquals(OrderStatus.FAILED, result.getStatus());
// 验证回滚库存
verify(inventoryService).decreaseStock(1L, 2);
verify(inventoryService).increaseStock(1L, 2);
verify(notificationService, never())
.sendOrderConfirmation(any(Order.class));
}
}
Controller 层测试
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<OrderResponse> createOrder(
@RequestBody OrderRequest request
) {
Order order = orderService.createOrder(request);
return ResponseEntity.ok(new OrderResponse(order));
}
}
// 测试类
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private OrderService orderService;
@Test
@DisplayName("POST /api/orders - 成功创建订单")
void testCreateOrder() throws Exception {
// Given
OrderRequest request = new OrderRequest();
request.setProductId(1L);
request.setQuantity(2);
Order mockOrder = new Order();
mockOrder.setId(1L);
mockOrder.setStatus(OrderStatus.PAID);
when(orderService.createOrder(any(OrderRequest.class)))
.thenReturn(mockOrder);
// When & Then
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"productId\":1,\"quantity\":2}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.status").value("PAID"));
verify(orderService)
.createOrder(any(OrderRequest.class));
}
}
八、最佳实践

1. 优先 Mock 接口而非具体类
// 推荐:Mock 接口
UserRepository userRepository =
mock(UserRepository.class);
// 不推荐:Mock 具体类
// 可能遇到 final 方法问题
UserRepositoryImpl userRepository =
mock(UserRepositoryImpl.class);
2. 避免过度 Mock
// 过度 Mock - 测试变得脆弱
@Test
void badTest() {
when(dependency1.method1()).thenReturn("value1");
when(dependency2.method2()).thenReturn("value2");
when(dependency3.method3()).thenReturn("value3");
// ... 大量的 Mock 设置
service.doSomething();
// 验证大量细节
verify(dependency1, times(2)).method1();
verify(dependency2).method2();
// ...
}
// 适度 Mock - 只关注核心交互
@Test
void goodTest() {
// 只 Mock 必要的依赖
when(userRepository.findById(1L))
.thenReturn(Optional.of(testUser));
User result = userService.getUser(1L);
assertEquals("张三", result.getName());
verify(userRepository).findById(1L);
}
3. 使用 @InjectMocks 自动注入
// 推荐:使用注解自动注入
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
// 不推荐:手动创建(容易出错且繁琐)
UserService userService =
new UserService(userRepository, emailService, ...);
4. 合理使用 ArgumentCaptor
// 推荐:验证复杂对象的属性
ArgumentCaptor<User> captor =
ArgumentCaptor.forClass(User.class);
verify(userRepository).save(captor.capture());
assertEquals("张三", captor.getValue().getName());
// 不推荐:简单参数使用 Captor
// 直接匹配即可
ArgumentCaptor<String> captor =
ArgumentCaptor.forClass(String.class);
verify(service).process(captor.capture());
assertEquals("value", captor.getValue());
5. 验证重要交互,忽略次要细节
// 推荐:只验证核心业务逻辑
@Test
void testUserRegistration() {
userService.register(user);
verify(userRepository).save(user);
verify(emailService).sendWelcome(user.getEmail());
// 不验证日志记录等次要操作
}
// 不推荐:验证过多细节
verify(logger).info("开始注册用户");
verify(validator).validate(user);
verify(logger).debug("用户验证通过");
// ... 测试变得脆弱
6. 使用有意义的测试数据
// 推荐:清晰的测试数据
User testUser = new User(
"张三",
"zhang@example.com",
25
);
// 不推荐:无意义的数据
User testUser = new User(
"test",
"test@test.com",
0
);
7. 一个测试方法只测一个场景
// 推荐:分离测试场景
@Test
void testCreateUser_Success() {
// 测试成功场景
}
@Test
void testCreateUser_DuplicateEmail() {
// 测试邮箱重复场景
}
@Test
void testCreateUser_InvalidInput() {
// 测试无效输入场景
}
// 不推荐:在一个测试中测试多个场景
@Test
void testCreateUser() {
// 成功场景
// 失败场景
// 异常场景
// ... 难以定位问题
}
九、总结
Mockito 是 Java 单元测试的利器,掌握它可以显著提升测试质量和开发效率。
更多推荐

所有评论(0)