1. 概述
在这篇文章中,我们将学习如何使用 **ArgumentMatcher
**,并讨论它与 ArgumentCaptor
的区别。
如果你对 Mockito 框架还不太熟悉,可以先参考 这篇文章 做个入门了解。
2. Maven 依赖
我们需要添加如下依赖项:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
最新版本的 Mockito 可在 Maven Central 上找到。
3. ArgumentMatchers 基础用法
我们可以用多种方式配置 Mock 方法的行为。一种常见方式是让它返回一个固定值:
doReturn("Flower").when(flowerService).analyze("poppy");
上面这段代码表示:当 flowerService
的 analyze
方法接收到字符串 "poppy"
时,返回 "Flower"
。
但有时候我们可能需要对更广泛或未知的输入值做出响应。这时候就可以使用 ArgumentMatchers 来灵活配置:
when(flowerService.analyze(anyString())).thenReturn("Flower");
现在无论传入什么字符串,都会返回 "Flower"
。这就是 ArgumentMatchers 的强大之处 —— 它让我们的 Stubbing 和 Verification 更加灵活。
⚠️ 注意:如果一个方法有多个参数,不能只对其中一部分使用 ArgumentMatchers,必须全部使用 Matcher 或全部使用具体值。
举个 ❌ 错误的例子:
when(flowerService.isABigFlower("poppy", anyInt())).thenReturn(true);
运行如下测试会抛出异常:
assertThrows(InvalidUseOfMatchersException.class,
() -> when(flowerService.isABigFlower("poppy", anyInt())).thenReturn(true));
✅ 正确做法是使用 eq()
匹配器来匹配具体的值:
when(flowerService.isABigFlower(eq("poppy"), anyInt())).thenReturn(true);
验证如下:
when(flowerService.isABigFlower(eq("poppy"), anyInt())).thenReturn(true);
Flower flower = new Flower("poppy", 15);
Boolean response = flowerController.isABigFlower(flower);
assertThat(response).isTrue();
使用 ArgumentMatchers 的注意事项:
- ❌ 不能将 ArgumentMatchers 作为返回值使用,Stubbing 时必须使用具体值。
- ❌ 不能在 Verification 或 Stubbing 外部使用 ArgumentMatchers。
如果误用,Mockito 会检测到并抛出 InvalidUseOfMatchersException
异常。
举个 ❌ 错误示例:
flowerController.isAFlower("poppy");
String orMatcher = or(eq("poppy"), endsWith("y"));
assertThrows(InvalidUseOfMatchersException.class, () -> verify(flowerService).analyze(orMatcher));
✅ 正确写法应该是:
verify(flowerService).analyze(or(eq("poppy"), endsWith("y")));
Mockito 还提供了 AdditionalMatchers
,用于对 ArgumentMatchers 进行逻辑操作(如 not
、and
、or
),支持原始类型和对象类型。
4. 自定义 ArgumentMatcher
自定义 ArgumentMatcher 可以帮助我们写出更清晰、可维护的高质量测试代码。
比如我们有一个 MessageController
,它接收一个 MessageDTO
,然后创建一个 Message
对象交给 MessageService
处理。
我们最初的测试可能是这样的:
MessageDTO messageDTO = new MessageDTO();
messageDTO.setFrom("me");
messageDTO.setTo("you");
messageDTO.setText("Hello, you!");
messageController.createMessage(messageDTO);
verify(messageService, times(1)).deliverMessage(any(Message.class));
由于 Message
是在被测方法内部构造的,所以只能使用 any()
进行匹配。
但这无法验证 Message
内部的数据是否与 MessageDTO
一致。
✅ 所以我们自定义一个 MessageMatcher
:
public class MessageMatcher implements ArgumentMatcher<Message> {
private Message left;
// constructors
@Override
public boolean matches(Message right) {
return left.getFrom().equals(right.getFrom()) &&
left.getTo().equals(right.getTo()) &&
left.getText().equals(right.getText()) &&
right.getDate() != null &&
right.getId() != null;
}
}
然后在测试中使用 argThat()
:
MessageDTO messageDTO = new MessageDTO();
messageDTO.setFrom("me");
messageDTO.setTo("you");
messageDTO.setText("Hello, you!");
messageController.createMessage(messageDTO);
Message message = new Message();
message.setFrom("me");
message.setTo("you");
message.setText("Hello, you!");
verify(messageService, times(1)).deliverMessage(argThat(new MessageMatcher(message)));
现在我们就能确保构造出的 Message
与 MessageDTO
的数据一致。
5. 自定义 ArgumentMatcher vs ArgumentCaptor
两者都可以用来验证 Mock 方法是否被调用时传入了特定参数。
- 如果你需要 断言参数的具体值,或者你的自定义 ArgumentMatcher 不会被复用,那么推荐使用
ArgumentCaptor
。 - 如果你需要 复用匹配逻辑,或用于 Stubbing,那么自定义
ArgumentMatcher
是更好的选择。
6. 对 Varargs 的支持增强(Mockito 5)
Mockito 5 在处理可变参数(如 String... args
)时做了显著改进,解决了以前匹配行为不一致的问题。
在早期版本中:
- 匹配零个或多个参数没问题;
- 但匹配恰好一个参数时会出问题。例如
when(mock.call(any()))
会匹配任意数量的参数,因为any()
会作用于每个元素而非整个数组。
✅ Mockito 5 引入了 type()
方法,可以更精确地控制匹配行为:
- 匹配任意数量参数:
when(mock.call(any(String[].class)))
- 匹配无参数调用:
when(mock.call())
- 匹配恰好一个参数:
when(mock.call(any()))
或when(mock.call(any(String.class)))
- 匹配恰好两个参数:
when(mock.call(any(), any()))
这使得 varargs 匹配更加直观和可预测。
ArgumentCaptor 对 Varargs 的增强
Mockito 5 中,ArgumentCaptor
也支持捕获整个数组或单个元素,并且支持泛型类型匹配:
@Captor private ArgumentCaptor<String> captor;
@Captor private ArgumentCaptor<String[]> arrayCaptor;
还可以捕获特定子类型。例如方法接收 Collection<?>
,但你只想捕获 List
:
ArgumentCaptor<Collection<?>> captor = ArgumentCaptor.forClass(List.class);
verify(mock).simpleMethod(captor.capture());
assertThat(captor.getAllValues()).containsExactly(List.of());
这些改进让 Mockito 在处理可变参数和泛型集合时更加清晰、安全、精确。
7. 总结
本文我们深入探讨了 Mockito 的 ArgumentMatcher
,并对比了它与 ArgumentCaptor
的使用场景。同时介绍了 Mockito 5 在 varargs 支持方面的改进,使测试代码更简洁、更可靠。