在Matchers.eq中调用spy方法时,参数匹配器的使用无效

时间:2016-12-28 14:35:51

标签: java junit mockito

在单元测试期间,我们遇到了来自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));

任何人都可以解释我为什么将它移动到变量?

1 个答案:

答案 0 :(得分:1)

这是因为Mockito matchers work via side-effects。这意味着Matcher调用不应该包含复杂的表达式,并且绝对不应该包含调用Mockito模拟或间谍的表达式。

对于像Java这样的强类型语言,Mockito无法在中轻松地表示或编码eq(0)gt(5)anyInt()这样的概念。区分看起来像int 的返回值,所以Mockito甚至都没有尝试。相反,Mockito将状态保持在隐藏的堆栈上,并依赖于方法调用的巧妙和精细的排序来操作和查询它:

  1. 一次调用verify,传入一个对象
  2. 对匹配器方法的任意数量的调用
  3. 对Mockito模拟对象上的方法进行一次调用
  4. 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]
    
    1. webSocketUtils无需评估,因此首先正确调用verify
    2. capture就像一个匹配器,所以这仍然是正确的
    3. webSocket是间谍,因此getUserNameWebSocketSessions()是对模拟的调用。哎呦!现在Mockito认为这是你正在验证的电话,你已经准备好了无参数方法调用的匹配器。看起来无效使用匹配器!
    4. 此时执行暂停,所以你甚至无法调用你的第二个匹配器[5]或你想要模拟的方法[6]。

      修好后:

      Collection<WebSocketSession> sessions =
          webSocket.getUserNameWebSocketSessions().get("user1");
      //  [1]
      Mockito.verify(webSocketUtils).sendMessageToMultipleUsers(
      //      [2]                    [5]
          messageCaptor.capture(), Matchers.eq(sessions));
      //                [3]                 [4]
      
      1. 对Mockito控制的对象的调用发生,就像您的被测系统制作它们一样。
      2. verify在正确的时间发生。
      3. capture将一个匹配器放入堆栈。
      4. eq将另一个匹配器放入堆栈。
      5. 发生了对Mockito模拟的调用,完成了模式。 Mockito使用两个匹配器对两个参数执行验证。