簡體   English   中英

在Matchers.eq中調用間諜方法時無效使用參數匹配器

[英]Invalid use of argument matcher when calling spy method inside Matchers.eq

在單元測試期間,我們遇到了來自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.

如您所見,第一個參數是捕獲器,第二個參數是Mockito包中的Matcher 我不知道為什么它不起作用,但是解決它的原因是將獲取會話從Matchers.eq()方法轉移到變量聲明。

固定

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

誰能解釋我為什么將其移至變量?

這是因為Mockito匹配器通過副作用起作用 這意味着Matcher調用不應包含復雜的表達式,並且絕對不應包含調用Mockito模擬或間諜的表達式。

對於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就像匹配器一樣,所以這仍然是正確的
  3. 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]
  1. 發生對Mockito控制對象的調用,與被測系統進行調用的過程完全相同。
  2. verify在正確的時間發生。
  3. capture將一個匹配器放入堆棧。
  4. eq將另一個匹配器放到堆棧中。
  5. 發生對Mockito模擬的調用,從而完成了模式。 Mockito使用您的兩個匹配器對兩個參數執行驗證。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM