Introduction to mockito-scala — Part 1

Mockito is a great tool, but every time I used it in Scala I could feel the pain of its Java API, sometimes the syntax was ugly, sometimes I had to create an alias for an import and some times it just didn’t work!

Because all of this is that I decided to create a library that could solve as many of this issues as it could be possible, and that’s how mockito-scala was born

The first thing to notice is that it lives inside the Mockito organisation in github, I’ve been working closely with the creators of Mockito to get this library started and I’m also working with them in order to add extension points or hooks in the core library to allow me implement features required by mockito-scala.

In this article I’ll refer to mockito-core (the Java one) as VanillaMockito or simply Mockito and MockitoScala to… well mockito-scala


API improvements

100% of the methods provided my org.mockito.Mockito are now provided by the trait org.mockito.MockitoSugar, yeah, yeah, I borrowed the name from Scalatest, but Scalatest’s version only provides the different variants of the mock[T] method, we on the other hand, provide the full API.

The first thing that we did was to provide the same ability of removing the need of using classOf[T] to create a mock, just like org.mockito.MockitoSugar does

In case you haven’t used it let me explain, imagine we have a class like

class MyClass {
def myMethod = "foo"
def anotherMethod(param: String) = param
}

and we want to mock it

With VanillaMockito we would have to write mock(classOf[MyClass])

Now with MockitoScala we just write mock[MyClass]

Not something that drop your jaw (and as I said, I know Scalatest’s MockitoSugar did that already), but is a start, it’s more concise, and it’s also simpler

What org.scalatest.MockitoSugar doesn’t provide is the same feature for doThrow[T] and argumentCaptor[T], so now we can write stuff like

val captor = argumentCaptor[String]
//Or
doThrow[IllegalArgumentException].when(myMock).myMethod

Instead of

val captor = ArgumentCaptor.forClass(classOf[String])
//Or
doThrow(classOf[IllegalArgumentException]).when(myMock).myMethod

Another cool thing that getting rid of classOf[T] allows us to do is to use inline mixins, so imagine we have this piece of code

trait MyTrait {
def traitMethod = "trait"
}
class MyClass {
def classMethod = "class"
}

And we want to create a mock that implements both, with VanillaMockito we could try to write something like

val myMock = mock(classOf[MyClass with MyTrait]) 
--------------
error: class type required but MyClass with MyTrait found
classOf[MyClass with MyTrait]

Ooops! it doesn’t compile!

Even if we try to do it with Scalatest’s MockitoSugar, it will compile, but in runtime we’ll get something like

val myMock = mock[MyClass with MyTrait]
myMock.traitMethod
----------------------
MyClass$MockitoMock$1674359400 cannot be cast to MyTrait
java.lang.ClassCastException: ....

Now, if we were using MockitoScala, we could write something like this

val myMock = mock[MyClass with MyTrait]

And will work!

This is because we do some extra reflection magic to find out the traits declared inline and leverage on a feature of Mockito that allows us to add extra interfaces to our mock! all of that hassle-free and with a neat syntax I’d say.

Java-Scala interoperability

If you used Scala long enough you have probably found yourself already in a situation where you wanna call a Java method and it doesn’t work for some reason.

A well known one happens when you want to call a method that is both
a) Overloaded
b) One of its implementations has varargs

We don’t need to go too far to find an example, we can find one in Mockito -why would I’ve mentioning it otherwise, right?- Anyway, if we look at the original doReturn methods, we would see something like

public static Stubber doReturn(Object toBeReturned)
public static Stubber doReturn(Object toBeReturned, Object... toBeReturnedNext)

As you can see, it meets both conditions, it’s overloaded and the second implementation has varargs, so if we try to call it from Scala we would get an amazing compiler error

class MyClass {
def myMethod = "foo"
}
val myMock = mock[MyClass]
Mockito.doReturn("hi").when(myMock).myMethod
---------
Error:(10, 76) ambiguous reference to overloaded definition,
both method doReturn in class Mockito of type (x$1: Any, x$2: Object*)org.mockito.stubbing.Stubber
and method doReturn in class Mockito of type (x$1: Any)org.mockito.stubbing.Stubber
match argument types (String)

Now, if we use the version provided by MockitoScala

class MyClass {
def myMethod = "foo"
}
val myMock = mock[MyClass]
MockitoSugar.doReturn("hi").when(myMock).myMethod

It will work as expected

Notice that here I’ve used MockitoSugar as an object, this is possible because the companion object of MockitoSugar also extends the trait.

object MockitoSugar extends MockitoSugar

This is so we can choose to mixin the trait in our test class or call the methods directly through the companion object, whatever you prefer.

Spying Lambdas

Yeah, yeah, I know spy(s) are usually a code smell, but hear me out, some times there are appropriate uses for them, so let’s forget that argument for the time being and let’s agree that we should have them available, all right?

Anyway, using the standard spy(myInstance) will not work on a function, so if we try something like the following snippet, we would get a “nice” runtime error

val aSpy = spy(() => "hola!")
-------
Cannot mock/spy class org.mockito.MockitoSugarTest$$Lambda$422/318288344
Mockito cannot mock/spy because :
- final class
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class org.mockito.MockitoSugarTest$$Lambda$422/318288344
Mockito cannot mock/spy because :
- final class

This is because Functions are implemented as final anonymous classes, and as the error says, Mockito can’t spy those…

Now, there is a ninja trick that works for this kind of compiler-generated final classes -so we could potentially spy other stuff apart from lambdas
As you have probably noticed already, one of the main aims of this library is to hide this kind of ninja tricks and provide a nice API instead, and this is not the exception, so if you do something like

val aSpy = spyLambda(() => "hola!")

It will work as expected :)

Default arguments

Default arguments are a neat and concise way to avoid having multiple overloaded implementations of the same method just for the sake of defaulting some parameters, now, this is concept that is completely alien to Java, so it’s to no surprise that VanillaMockito doesn’t play very well with them.

The problem here arises when we try to verify that our production code has called some method that uses default arguments (and said code do not pass any value for such parameter(s)), e.g.

If we run that, we will get a runtime exception like

Argument(s) are different! Wanted:
foo.iHaveSomeDefaultArguments(
"I'm not gonna pass the second argument",
"default value"
);

Actual invocation has different arguments:
foo.iHaveSomeDefaultArguments(
"I'm not gonna pass the second argument",
null
);

Note: this is a bunch of low-level stuff, if you don’t feel like reading it, feel free to jump to the next section knowing this issue is solved by the library, although I think is always nice to know how stuff works

Why does that happens? well, it’s related how the Scala compiler implements this as a Java class, remember that Scala runs on the JVM, so everything needs to be transformed to something that looks like Java

In this particular case, what the compiler does is to create a series of hidden methods which will be called something like methodName$default$<a number> where <a number> is the position of the argument this method is representing, then, the compiler will check every time we call this method and if we don’t provide a value for such parameter, it will insert a call to the$default$ method in its place, so in our example, the “compiled” version would be something like this (note that this is not exactly what the compiler does, but it works for educational purposes)

aMock.iHaveSomeDefaultArguments("I'm not gonna pass the second argument", aMock.iHaveSomeDefaultArguments$default$1)

Now, because we are making the call on a mock, and the default behaviour of Mockito is to return null for anything that hasn’t been stubbed, then what ends up executing is something like

aMock.iHaveSomeDefaultArguments("I'm not gonna pass the second argument", null)

Which is exactly what the error is telling us…

How do we solve this? well, doing a bit of reflection, we can look for any method whose name contains $default$ and pre-stub the mock to actually execute the real implementation of those methods (using the Mockito action thenCallRealMethod() ), so now the compiled method call looks like

aMock.iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value")

And hence, the verification works

By-Name arguments

Again, a lot of low level, mumbo jumbo, I think it’s always useful to know how things work under the hood, but feel free to jump to the examples of what works and what doesn’t if you aren’t interested

By-Name arguments are another great Scala-only feature that provides lazily evaluated arguments. This means that the substitution model (the job the complier does replacing anything that we pass as an argument by it’s value, like we saw in the previous example) will work differently here.

Let’s imagine we have something like this

That code will fail in line 10 saying something like

null was not equal to "mocked!"
Expected :"mocked!"
Actual :null

Why this doesn’t work??

First of all notice in line 8 that we instruct Mockito to return "mocked!" only when the strings “arg1” and “arg2” are passed as arguments, now, by-name arguments, after the compiler works it’s magic, are not exactly strings anymore, they are transformed into functions that return a string (or whatever your type is), so our method call would look like the following after the compiler worked out the transformations

aMock.iStartWithByNameArgs("arg1", () => "arg2") shouldBe "mocked!"

So, from the Mockito point of view, the string “arg2” and a function that returns the string “arg2” are not the same, so the stubbing does not match and boom, we get null as it is the default answer for a non stubbed invocation.

All the possible combinations in how/why this can bite us can fill an entire article of it’s own, so I’ll leave it for the future, but for now, please know that MockitoScala allows you to stub and verify methods that use by-name arguments in almost every scenario, so be aware that it has

  • Full support when all arguments in a method are by-name, i.e. you can use matchers or pass all the raw values for stubbing or verifying and it will work.
  • Full support when only some arguments in a method are by-name, but we use the any[T] matcher for every argument, i.e. if we have a mix of normal and by-name parameters, but we use the any[T] matcher for all of them, then it will work
  • Full support when only some arguments in a method are by-name, but we use NO matchers at all, similar to the previous one, if we just use raw values then it will work
  • Partial support when only some arguments in a method are by-name and we use specific matchers, in this scenario the stubbing will only work if the by-name arguments are the last ones in the method signature.

That’s all for now, in the next article, I talk about the ArgumentMatchers and how MockitoScala improves them. Thanks for reading!