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
When I tried to mock
customerId method (property?):
in my test, Mockito started complaining about wrong return types and refused to cooperate:
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
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:
asInstanceOf[Object] to fool Scala’s type system
and then return a
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:
But I saw the results I was completly perplexed.
RequestId(123L) worked but the one with
any() did not. But what is worse the second verification
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
For example, in Scala REPL:
any() matcher that comes with Mockito has a very simple
that always returns
The method that I tried to check, has a signature:
RequestId is a value class, the “real” JVM signature is:
When I write
Scala compiler adds instructions that convert the object returned
any() (remember generics does not exist on JVM level, so all
Vs are just
Objects at runtime) to
And this is the reason why I got
In bytecode it looks like this:
and the NPE is thrown by the instruction at the offset
Now I understand the problem, but I still want to use
There must be a trick to make it return a valid
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
RequestId. What I really need is
a way to use
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:
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
Mockito will work. Actually we may wrap any matcher in
as many dummy calls as we want, as in
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:
WTF? Not again… Another strange problem that forces me to look under the bonnet.
Let’s look at the bytecode using
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
If only I could stub this
method$default$2 or something
…oh wait I could:
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:
I plug it into my project (it even has a special version for ScalaTest) and suddenly everything started to working as it should be.
…and verification works:
even default parameters work:
Wanna see some real code? Click here.