2016-12-28 52 views
0

间谍法在单元测试中,我们已经遇到了一个奇怪的错误从Mockito,这第一眼看起来微不足道,但经过深入了解,我们无法找到它为什么有效无效使用的参数匹配的像那样。请看下面的代码和抛出的错误。打电话时,里面Matchers.eq

代码

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. 

正如你所看到的,第一个参数是捕手,第二个是从MockitoMatcher。我不知道为什么它不工作,但什么固定它,是从Matchers.eq()方法让移动会话变量声明。

固定

Collection<WebSocketSession> sessions = webSocket.getUserNameWebSocketSessions().get("user1"); 
Mockito.verify(webSocketUtils).sendMessageToMultipleUsers(messageCaptor.capture(), Matchers.eq(sessions)); 

任何人都可以解释我为什么我不得不将它移到一个变量?

回答

1

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

对于Java等强类型语言,有没有办法的Mockito代表或编码像,看起来像一个int易于区分返回值eq(0)gt(5)anyInt()一个概念,所以的Mockito没有按别尝试。相反,的Mockito保持在一个隐藏的叠加状态,并依靠聪明和微妙的排序方法的调用来操作和查询它:

  1. 一个调用verify,在对象
  2. 任意数量的电话传递给匹配方法
  3. 一个呼叫到的Mockito-嘲笑对象上的方法

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就像一个匹配,所以这仍然是正确的因为是一个模拟调用。哎呦!现在Mockito认为这是您正在验证的电话,并且您已经为无参数方法调用准备好了匹配器。看起来像匹配器的无效使用!

此时执行停止,所以你甚至不会打电话给你的第二个匹配器[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使用两个匹配器对两个参数执行验证。