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");

上面这段代码表示:当 flowerServiceanalyze 方法接收到字符串 "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 进行逻辑操作(如 notandor),支持原始类型和对象类型。

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)));

现在我们就能确保构造出的 MessageMessageDTO 的数据一致。

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 支持方面的改进,使测试代码更简洁、更可靠。



原始标题:Mockito ArgumentMatchers | Baeldung

« 上一篇: Spring 核心注解
» 下一篇: Spring 中集合的注入