2

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?

Richard Keene
  • 398
  • 3
  • 14
  • 1
    Debugging is a "heavy intervention" into "concurrency" ..(you "suspend" something)..and yes, having some "Watches" or "Variables" can also issue `toString()`. – xerx593 Oct 05 '22 at 18:48
  • 2
    @xerx593 multiple `thenReturn`s set up results returned from consecutive calls. First call will return first value, second call will return second value. Third call will return first again (IIRC) – knittl Oct 05 '22 at 18:53
  • 2
    @knittl Close, the last call will repeat forever as long as you don't override it with another later matching call to `when`. You can have `.thenReturn(t1, t2)`, or `.thenReturn(t1).thenReturn(t2)` as above, or even interleave `thenThrow` or `thenAnswer`. – Jeff Bowman Oct 06 '22 at 21:10

1 Answers1

5

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.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251