在单元测试期间,我们遇到了来自Mockito
的奇怪错误,乍一看可能看起来微不足道,但经过深入研究后,我们无法找到原因。请查看以下代码并抛出错误。
代码
import org.assertj.core.api.Assertions;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
//...
@InjectMocks
@Spy
private MyWebSocket webSocket;
@Mock
private WebSocketUtils webSocketUtils;
@Test
public void myTest() throws IOException {
WebSocketSession session1 = Mockito.mock(WebSocketSession.class);
WebSocketSession session2 = Mockito.mock(WebSocketSession.class);
WebSocketSession session3 = Mockito.mock(WebSocketSession.class);
webSocket.getUserNameWebSocketSessions().put("user1", session1);
webSocket.getUserNameWebSocketSessions().put("user1", session2);
webSocket.getUserNameWebSocketSessions().put("user2", session3);
Mockito.doReturn(new CustomWebSocketMessage<UserSessionInfo>().config(Command.USER_SESSION_INFO).data(new UserSessionInfo()))
.when(webSocket)
.buildUserSessionInfoWebSocketMessage(Mockito.any());
webSocket.onUserPermissionsChange(new UserPermissionsChangedEvent(Collections.singletonList("user1"), this));
ArgumentCaptor<CustomWebSocketMessage> messageCaptor = ArgumentCaptor.forClass(CustomWebSocketMessage.class);
Mockito.verify(webSocketUtils).sendMessageToMultipleUsers(messageCaptor.capture(), Matchers.eq(webSocket.getUserNameWebSocketSessions().get("user1")));
CustomWebSocketMessage message = messageCaptor.getValue();
Assertions.assertThat(message.getMessagePayload().getConfig().getCommand()).isEqualTo(Command.USER_SESSION_INFO.name());
Assertions.assertThat(message.getMessagePayload().getData()).isInstanceOf(UserSessionInfo.class);
}
错误
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
0 matchers expected, 1 recorded:
-> at ... (Name of the bussines package)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
如您所见,第一个参数是捕获者,第二个参数是Matcher
包中的Mockito
。我不知道为什么它不起作用,但修正它的方法是将会话从Matchers.eq()
方法转移到变量声明。
固定
Collection<WebSocketSession> sessions = webSocket.getUserNameWebSocketSessions().get("user1");
Mockito.verify(webSocketUtils).sendMessageToMultipleUsers(messageCaptor.capture(), Matchers.eq(sessions));
任何人都可以解释我为什么将它移动到变量?
答案 0 :(得分:1)
这是因为Mockito matchers work via side-effects。这意味着Matcher调用不应该包含复杂的表达式,并且绝对不应该包含调用Mockito模拟或间谍的表达式。
对于像Java这样的强类型语言,Mockito无法在中轻松地表示或编码eq(0)
或gt(5)
或anyInt()
这样的概念。区分看起来像int
的返回值,所以Mockito甚至都没有尝试。相反,Mockito将状态保持在隐藏的堆栈上,并依赖于方法调用的巧妙和精细的排序来操作和查询它:
verify
,传入一个对象 when
存根具有类似的规则(零或更多匹配器,一次调用模拟方法,一次调用when
,一次或多次调用thenVerb
), doVerb
存根(一次调用doVerb
,零次或多次调用thenVerb
,一次调用when
,零次或多次匹配,一次调用模拟方法。但是,Mockito无法在您的测试方法中看到代码,因此它无法分辨下一步会发生什么;它只能通过调用静态方法或在模拟上调用方法来查看与Mockito交互的时间。这意味着有时它不会给你足够的异常(测试错误地传递),有时它会给你太多(比如这里),有时它会给你错误的异常消息或者在错误的方法上标记异常。
我将使用我在 Mockito匹配器工作方式中使用的注释约定吗?上面链接的答案,其中显示了Java在调用方法之前如何从左到右评估参数表达式。
在您的特定情况下,您的原始方法遵循以下模式:
Mockito.verify(webSocketUtils).sendMessageToMultipleUsers(
// [1] [6]
messageCaptor.capture(),
// [2]
Matchers.eq(webSocket.getUserNameWebSocketSessions().get("user1")));
// [5] [3] [4]
webSocketUtils
无需评估,因此首先正确调用verify
capture
就像一个匹配器,所以这仍然是正确的webSocket
是间谍,因此getUserNameWebSocketSessions()
是对模拟的调用。哎呦!现在Mockito认为这是你正在验证的电话,你已经准备好了无参数方法调用的匹配器。看起来无效使用匹配器!此时执行暂停,所以你甚至无法调用你的第二个匹配器[5]或你想要模拟的方法[6]。
修好后:
Collection<WebSocketSession> sessions =
webSocket.getUserNameWebSocketSessions().get("user1");
// [1]
Mockito.verify(webSocketUtils).sendMessageToMultipleUsers(
// [2] [5]
messageCaptor.capture(), Matchers.eq(sessions));
// [3] [4]
verify
在正确的时间发生。capture
将一个匹配器放入堆栈。eq
将另一个匹配器放入堆栈。