Introduction to mockito-scala — Part 2
This article series was written for the version 0.x.x of mockito-scala, and while the technical details and reasoning behind the new features still holds true, some parts of the API were changed from 1.x.x, so I strongly recomend to have a read to the README where the changes are explained.
It’s also worth to check said README to discover the new features introduced in 1.x.x
In the first article we introduced the library and its improvements over the Java Mockito API.
Now we will see how the ArgumentMatchers
are also improved by it
The first thing to notice is that we have the trait org.mockito.ArgumentMatchersSugar
which again provides all the Matchers available in VanillaMockito plus a few more, it also improves the syntax for many of them.
It’s companion object also extends the trait, so if you don’t want to mix-it-in you can access them through it.
Naming clashes
If you’re like me, the first thing that would have give you problems with the matchers is the eq
matcher, as you have to do a specific import alias to overcome the fact that eq
is a method that already exists in every Scala object for doing reference equality, so basically you end up doing something like
import org.mockito.ArgumentMatchers.{eq => eqTo, _}
Over and over again, which I found really annoying…
So to fix that, the matcher method exposed by ArgumentMatchersSugar
is already called eqTo[T]
so you don’t have to bother about it.
Type inference and parenthesis
Another thing I found a bit cumbersome is that the Java API is inconsistent regarding when you can leave the parenthesis off or not, and wether you have to be explicit with the type or not, i.e. if you use any
without parenthesis nor [Type]
it will fail to compile with an error like
when(myMock.myMethod(any, any) thenReturn result------------Error:(85, 46) polymorphic expression cannot be instantiated to expected type;
found : [T]()T
required: String
when(myMock.myMethod(any, any) thenReturn result
So in that particular case you could fix it by either writing any()
or any[String]
or anyString
and its variants with explicit parenthesis.
Similar things happen with many other methods around the VanilaMockito API, so, to overcome this problem and make the API more consistent, error-free and readable is that all the parameterless methods exposed by mockito-scala do not require parenthesis at all, making your code less likely to fail because you forgot a bloody parenthesis or a type, so the line when(myMock.myMethod(any, any) thenReturn result
will actually work as expected.
Null matchers
Null matchers are marked as deprecated in mockito-scala, as you shouldn’t be using nulls anywhere in a decent Scala codebase, so the idea is to caught your attention to that with the compiler warning
Function matchers
There is a new matcher called function0
that allows you to test equality over no-args functions, the idea is that you could write something like
class Foo {
def iHaveFunction0[T](v: () => T): T = v()
}val aMock = mock[Foo]
aMock.iHaveFunction0(() => "meh")
verify(aMock).iHaveFunction0(function0("meh"))
And so verify that your mock got called with a function that somehow returns the expected value.
Value class matchers
All right, you can read more about value classes here, but basically value classes only exist at compile time, to help us use the compiler to type check stuff -and also to make the code more explicit/readable- that otherwise would be simple, meaningless, types like an Int, String, etc without paying the overhead of creating this simple, stateless wrapper objects.
The fact that they don’t exist in runtime (and also that you can’t pass null when a value class is expected, as they are treated as values) makes them particularly annoying to use with VanillaMockito
What you have to do if you are using Matchers is to put them inside an instance of the value class itself, e.g. if we were using eqTo
then we would have to do something like when(myMock.myMethod(MyValueClass(eqTo(someValue)) thenReturn <something>
instead of when(myMock.myMethod(eqTo(MyValueClass(someValue))) thenReturn <something>
Which reads a bit odd, and it’s so anti-natural that if you don’t find that solution in stack overflow you could spend a long time trying to get them working.
What we want to achieve is to avoid as much of that boilerplate as we can, for this we use a combination of Macros and Type Classes (in the Scala docs they are called implicit macros)
So, given a value class case class MyValueClass(v: String) extends AnyVal
what we want is a magic thing that constructs an instance of MyValueClass
wrapping the matcher we want to use (currently only any
and eqTo
are supported)
This is where macros can help us, so we defined the trait
trait ValueClassMatchers[T] { def anyVal: T def eqToVal(v: Any): T}
And we want a macro that given a user type, in this case MyValueClass
it inspects it (it’s like doing reflection but at compile time) and creates an implementation that looks like (I’ve simplified the code for readability)
new ValueClassMatchers[MyValueClass] {
def anyVal: MyValueClass = new MyValueClass(ArgumentMatchers.any[String]())
def eqToVal(v: Any): MyValueClass = new MyValueClass(ArgumentMatchers.eq[String](v))
}
Now, how do we get that instance tailored for our type (or how the compiler knows for what type it needs to create this), well this is where the TypeClass comes into play
For example, if you look at the anyVal[T]
method in the ArgumentMatchersSugar
trait, it has the following signature
def anyVal[T](implicit valueClassMatchers: ValueClassMatchers[T]): T = valueClassMatchers.anyVal
So basically when we do anyVal[MyValueClass]
in our test code, the compiler will look for an implicit instance of the type ValueClassMatchers[MyValueClass]
In order to solve the implicit lookup, the companion object of ValueClassMatchers has the following method
implicit def materializeValueClassMatchers[T]: ValueClassMatchers[T] = macro materializeValueClassMatchersMacro[T]
That acts as an implicit factory for instances of that TypeClass, in where the actual definitions/instances are created by the macro.
So with all of that we get to a point where we can now write something like this (notice that for this matchers we must provide the type)
class Foo {
def valueClass(v: ValueClass): String = v.v
}when(aMock.valueClass(anyVal[ValueClass])) thenReturn "mocked!"aMock.valueClass(ValueClass("meh")) shouldBe "mocked!"verify(aMock).valueClass(eqToVal[ValueClass]("meh"))
And it works!
So far those are the only 2 matchers that support value classes (anyVal[T]
and eqToVal[T]
) as I couldn’t think valid use-cases for the others, but if you need another one just open an issue in github and we’ll try to get it working.
That’s it, thanks for reading and I hope you enjoy the library!
Please check Part 3 for more exciting features