概述
在这个教程中,我们将学习如何使用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中央仓库找到。
3. ArgumentMatchers
我们可以以多种方式配置mocked方法。一种选择是返回固定值:
doReturn("Flower").when(flowerService).analyze("poppy");
在上面的例子中,只有当FlowerService
的analyze
方法接收到字符串"poppy"时,才会返回字符串"Flower"。
然而,有时可能需要对更广泛的值或未知值做出响应。
在这种情况下,我们可以通过参数匹配器来配置我们的mocked方法:
when(flowerService.analyze(anyString())).thenReturn("Flower");
现在,由于使用了anyString
参数匹配器,无论我们传递什么值给analyze
方法,结果都会相同。ArgumentMatchers
使我们能够灵活地进行验证或模拟。
如果一个方法有多个参数,我们不能只对其中一些参数使用ArgumentMatchers
。Mockito要求我们为所有参数提供匹配器或确切的值。
以下是一个不正确的示例:
when(flowerService.isABigFlower("poppy", anyInt())).thenReturn(true);
我们可以通过运行下面的测试来验证这一点:
assertThrows(InvalidUseOfMatchersException.class,
() -> when(flowerService.isABigFlower("poppy", anyInt())).thenReturn(true));
要解决这个问题,并保持字符串名称"poppy",我们将使用eq matcher
:
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();
当我们使用匹配器时,还有两点需要注意:
- 我们不能用它们作为返回值;在模拟调用时,我们需要确切的值。
- 我们不能在验证或模拟之外使用参数匹配器。
根据第二点,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. 定制参数匹配器
创建我们自己的匹配器允许我们在特定场景中选择最佳方法,从而编写出高质量、易于维护的测试。
例如,我们有一个MessageController
,负责发送消息。它会接收一个MessageDTO
,然后从中创建一个Message
,由MessageService
发送。
我们的验证将很简单:我们只需确认我们精确地向MessageService
调用了1次,传入任何Message:
:
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
内的数据不同。
因此,我们将实现一个自定义参数匹配器:
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;
}
}
为了使用我们的匹配器,我们需要修改测试,将any
替换为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. 自定义参数匹配器与ArgumentCaptor
两种技术,即custom argument matchers
和ArgumentCaptor
,都可以用来确保某些参数传递给mock。
但是,如果需要对参数值进行进一步的断言以完成验证,或者我们的custom argument matcher
不太可能重用,那么ArgumentCaptor
可能更适合。
通常,通过ArgumentMatcher
实现的自定义参数匹配器更适合于模拟。
6. 总结
在这篇文章中,我们探讨了Mockito中的ArgumentMatcher
功能。还讨论了它与ArgumentCaptor
的不同之处。
如往常一样,所有示例的完整源代码可在GitHub上找到。