1

During unit tests we've encountered a weird error from Mockito, that at the first glance might look trivial, but after deeper look we can't find why it works like that. Please take a look on the following code and an error that is thrown.

Code

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);
}

Error

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.

As you can see, the first argument is a captor, the second is Matcher from Mockito package. I have no idea why it doesn't work, but what fixed it, was to move getting sessions from Matchers.eq() method to a variable declaration.

Fixed

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

Can anyone explain me why I had move it to a variable ?

ashur
  • 4,177
  • 14
  • 53
  • 85

1 Answers1

1

It's because Mockito matchers work via side-effects. This means that Matcher calls shouldn't contain complex expressions, and definitely shouldn't contain expressions that call Mockito mocks or spies.

For a strongly-typed language like Java, there's no way for Mockito to represent or encode a concept like eq(0) or gt(5) or anyInt() in an easy-to-distinguish return value that looks like an int, so Mockito doesn't even try. Instead, Mockito keeps state on a hidden stack, and relies on a clever and delicate ordering of method calls to manipulate and query it:

  1. One call to verify, passing in an object
  2. Any number of calls to matcher methods
  3. One call to a method on a Mockito-mocked object

when stubs have similar rules (zero or more matchers, one call to a method on a mock, one call to when, one or more calls to thenVerb), as do doVerb stubs (one call to doVerb, zero or more calls to thenVerb, one call to when, zero or more matchers, one call to a method on a mock). However, Mockito can't see the code in your test method, so it can't tell what's coming next; it can only see when you interact with Mockito by calling a static method or calling a method on a mock. This means that sometimes it doesn't give you enough exceptions (tests pass erroneously), sometimes it gives you too many (like here), and sometimes it gives you the wrong exception message or flags the exception on the wrong method.


I'm going to use an annotation convention I used in my How do Mockito matchers work? answer linked above, which shows how Java evaluates parameter expressions left-to-right before calling the method.

In your particular case, your original method follows this pattern:

Mockito.verify(webSocketUtils).sendMessageToMultipleUsers(
//      [1]                    [6]
    messageCaptor.capture(),
//                [2]
    Matchers.eq(webSocket.getUserNameWebSocketSessions().get("user1")));
//           [5]          [3]                            [4]
  1. webSocketUtils needs no evaluation, so verify correctly gets called first
  2. capture acts like a matcher, so this is still correct
  3. webSocket is a spy, so getUserNameWebSocketSessions() is a call to a mock. Whoops! Now Mockito thinks that's the call you're verifying, and you've got a matcher ready for a parameterless method call. Looks like an invalid use of matchers!

Execution halts at this point, so you don't even get to call your second matcher [5] or the method you're intending to mock [6].

Once you fix it:

Collection<WebSocketSession> sessions =
    webSocket.getUserNameWebSocketSessions().get("user1");
//  [1]
Mockito.verify(webSocketUtils).sendMessageToMultipleUsers(
//      [2]                    [5]
    messageCaptor.capture(), Matchers.eq(sessions));
//                [3]                 [4]
  1. Calls to Mockito-controlled objects happen, exactly the same as if your system-under-test made them.
  2. verify happens at the correct time.
  3. capture puts one matcher onto the stack.
  4. eq puts the other matcher onto the stack.
  5. The call to the Mockito mock happens, completing the pattern. Mockito performs the verification using your two matchers for the two parameters.
Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251