Richard Keene
Richard Keene

Reputation: 402

Mockito single stepping fails?

Ok this is weird. Mockito, Java, on windows.

 when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), eq(Task.class)))
            .thenReturn(t1)
            .thenReturn(t2);

Now if I run this in debug mode it works fine. But if I put a break point on the when, and single step it fails.

The error in IntelliJ is

org.mockito.exceptions.misusing.WrongTypeOfReturnValue: 
Task cannot be returned by toString()
toString() should return String
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
   Please refer to Mockito FAQ on limitations of concurrency testing. 
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies - 
 - with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.

So this is possibly some interaction with IntelliJ wanting to "toString()" the result while single stepping. Maybe Mockito needs to catch and fallback on toString on OngoingStubbing?

Upvotes: 2

Views: 952

Answers (1)

Jeff Bowman
Jeff Bowman

Reputation: 95654

This behavior is intentional, and has to be, or else toString would be unstubbable or unusable.

Mockito stubbing works via side-effects and proxying. For a call like yours:

 when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), eq(Task.class)))
            .thenReturn(t1)
            .thenReturn(t2);

Mockito takes advantage of the predictable Java execution order, which evaluates method arguments in order and then the method call itself, so it sees:

  1. any(Query.class): 1 matcher on the argument matcher stack, return null
  2. any(Update.class): 2 matchers on the stack, return null
  3. any(FindAndModifyOptions.class): 3 matchers on the stack, return null
  4. eq(Task.class): 4 matchers on the stack, return null
  5. findAndModify(null, null, null, null): Return what findAndModify returns, findAndModify is now the most recent call
  6. when: we're now mocking most recent call findAndModify with four argument matchers, reset the stack
  7. thenReturn(t1): Add an action onto the stubbing chain that when returned
  8. thenReturn(t2): Add an action onto the stubbing chain that when returned

If your IDE calls toString between steps 5 and 6, then Mockito will assume that you called findAndModify(null, null, null, null) intentionally earlier and are trying to stub toString now. Since you're trying to return t1, that means that Mockito will tell you that your stub of toString() expects a String but receives a Task.

Though this is annoying, it's arguably less annoying than if Mockito refused to stub toString(), or if you were entirely unable to call toString() on a mock.

Upvotes: 5

Related Questions