Pitfalls of using Mockito with Scala

I need to test my new, shiny Scala code. Usually I write tests in ScalaTest, but for generating stubs I still use good, old Mockito. What can possibly go wrong? I open a new tab in my editor and start hacking test code.

For the first surprise I don’t have to wait too long. In my code I use value classes to represent entity IDs. For example I use CustomerId:

case class CustomerId(id: Long) extends AnyVal

When I tried to mock customerId method (property?):

trait RequestContext {
  val customerId: CustomerId
  // ...
}

in my test, Mockito started complaining about wrong return types and refused to cooperate:

// inside test
val requestContext = mock(classOf[RequestContext])

/* Fails with WrongTypeOfReturnValue exception:
*
* CustomerId cannot be returned by customerId()
* customerId() should return long
*/
when(requestContext.customerId).thenReturn(CustomerId(123L))

Of course my CustomerId value class is here to blame…

In Scala, value classes offer type safety of normal classes with performance of the primitives. Scala compiler achieves this, simply by replacing the value class type by the primitive type, wherever possible. So I grabbed javap and took a look at the generated bytecode, and indeed there is a long there:

target/scala-2.13/classes$ javap RequestContext.class 
Compiled from "RequestContext.scala"
public interface RequestContext {
  public abstract long customerId();
  // ...
}

Mockito uses reflection to figure out which type a given method returns. This gives us little hope for a nice solution. We can only try to hack around the problem, for example this monstrosity works:

val requestContext = mock(classOf[RequestContext])

when(requestContext.customerId.asInstanceOf[Object])
  .thenReturn(Long.box(123L))

assert(requestContext.customerId == CustomerId(123L))

We use asInstanceOf[Object] to fool Scala’s type system and then return a java.lang.Long instance.

Yeah it works but it is not nice… But hey, first get the job done, then get it done well. Let’s move on to the next test…

I have to verify that a given method was called. This time I should have more luck, right? So I wrote another test and I ran it:

val requestContext = mock(classOf[RequestContext])

requestContext.setRequestId(RequestId(123L))

// works perfectly 
verify(requestContext).setRequestId(RequestId(123L))

// does NOT WORK 
verify(requestContext).setRequestId(any())

But I saw the results I was completly perplexed. Verification with RequestId(123L) worked but the one with any() did not. But what is worse the second verification thrown NullPointerException. NPE? Really?

My brain was racing, and after a few seconds a thought strike me: it’s these pesky value classes again!

And I was right! Value classes in Scala cannot be null! For example, in Scala REPL:

scala> case class FooId(id: Long) extends AnyVal
defined class FooId

scala> val f: FooId = null.asInstanceOf[FooId]
f: FooId = FooId(0)

scala> f.id
res0: Long = 0

// And more...
scala> var o: Object = null
o: Object = null

scala> o.asInstanceOf[FooId]
java.lang.NullPointerException
  ... 33 elided

any() matcher that comes with Mockito has a very simple implementation:

public static <T> T any() {
  return anyObject();
}

@Deprecated
public static <T> T anyObject() {
  reportMatcher(Any.ANY);
  return null;
}

that always returns null.

The method that I tried to check, has a signature:

trait RequestContext {
  def setRequestId(requestId: RequestId)
  // ...
}

and since RequestId is a value class, the “real” JVM signature is:

public abstract void setRequestId(long);

When I write verify(...).setRequestId(any()) Scala compiler adds instructions that convert the object returned by any() (remember generics does not exist on JVM level, so all these Ts and Vs are just Objects at runtime) to long. And this is the reason why I got NullPointerException earlier.

In bytecode it looks like this:

19: invokestatic  #33 // Method org/mockito/Mockito.verify:(Ljava/lang/Object;)Ljava/lang/Object;
22: checkcast     #17 // class RequestContext
25: invokestatic  #39 // Method org/mockito/ArgumentMatchers.any:()Ljava/lang/Object;
28: checkcast     #41 // class RequestId
31: invokevirtual #45 // Method RequestId.id:()J
34: invokeinterface #29, 3 // InterfaceMethod RequestContext.setRequestId:(J)V

and the NPE is thrown by the instruction at the offset 31.

Now I understand the problem, but I still want to use any() matcher. There must be a trick to make it return a valid RequestId. But then I realized that even if I found such a way, I would be bitten again by WrongTypeOfReturnValue exception or something similar, since the method takes long not RequestId. What I really need is a way to use anyLong() with setRequestId. It was a good enough challenge to start my evil alter-ego working on some frankensteinian solution. And I found it, I FOUND IT!, I FOUND IT!!! Ehmm… and here it is:

val requestContext = mock(classOf[RequestContext])

requestContext.setRequestId(RequestId(123L))

// works again
verify(requestContext).setRequestId(RequestId(anyLong()))
verify(requestContext).setRequestId(
  RequestId(ArgumentMatchers.eq(123L)))

A perfect combination of beauty and evil…

The trick that I used here is that Mockito does not care, where the matcher is located, it only cares about the time when it is called. As long as I call anyLong() after the call to verify(...) and before the call to .setRequestId(...), Mockito will work. Actually we may wrap any matcher in as many dummy calls as we want, as in a(b(c(d(any())))), only the fact that it was called counts.

It can’t be worse right? Two tests, two hacks…

But only I wrote my third test, I was slapped by the next problem, this time caused by default parameters:

trait SomeTrait {
 def method(a: Int, b: Int = 100): Int
}

test("...") {
 val someTrait = mock(classOf[SomeTrait])

 someTrait.method(3)

 /* This call fails with:
 * Argument(s) are different! Wanted:
 *  someTrait.method(3, 100);
 * Actual invocations have different arguments:
 *  someTrait.method(3, 0);
 */
 // verify(someTrait).method(3, 100)

 // works
 verify(someTrait).method(3, 0)
}

WTF? Not again… Another strange problem that forces me to look under the bonnet.

Let’s look at the bytecode using javap -c:

// bytecode of `someTrait.method(3)`
// (some code skipped here)
// 3 - load first arg onto the stack
10: iconst_3

// Scala Magic(TM), call a method to get the second
// argument's default value and push it onto the stack:
// someTrait.method$default$2()
11: aload_0
12: invokeinterface #27,  1

// finally call the `method` method
17: invokeinterface #31,  3
// (some code skipped here)

So the Scala compiler calls a hidden method, with a name methodName$default$parameterIndex on the trait, to figure out what value should be used as a value of the default parameter. Wow! I didn’t expect something like that!

If only I could stub this method$default$2 or something :thinking: :thinking: :thinking: …oh wait I could:

val someTrait = mock(classOf[SomeTrait])
when(someTrait.method$default$2).thenReturn(100)

someTrait.method(3)

// it works!
verify(someTrait).method(3, 100)

Excellent. It is working perfectly but now I have three tests and three hacks. Surely I am doing something wrong here.

And so I harnessed the power of Google (after spending some time looking at the pictures of stoats; hey they are really cute) and found this gem:

Mockito-Scala

I plug it into my project (it even has a special version for ScalaTest) and suddenly everything started to working as it should be.

Stubbing works:

import org.mockito.ArgumentMatchersSugar._ // from Mockito-Scala
import org.mockito.MockitoSugar._
// Don't import Mockito or ArgumentMatchers

val requestContext = mock[RequestContext]
when(requestContext.customerId).thenReturn(CustomerId(123L))
assert(requestContext.customerId == CustomerId(123L))

…and verification works:

val requestContext = mock[RequestContext]

requestContext.setRequestId(RequestId(123L))

verify(requestContext).setRequestId(eqTo(RequestId(123L)))
verify(requestContext).setRequestId(any[RequestId])
// but this will not work: verify(requestContext).setRequestId(any)

even default parameters work:

val someTrait = mock[SomeTrait]
someTrait.method(3)
verify(someTrait).method(3)
verify(someTrait).method(3, 100)

Yay!

Wanna see some real code? Click here.

References: