Suppose I have this class:
class Defaults {
def doSomething(regular: String, default: Option[String] = None) = {
println(s"Doing something: $regular, $default")
}
}
I want to check that some other class invokes doSomething()
method on Defaults
instance without passing second argument:
defaults.doSomething("abcd") // second argument is None implicitly
However, mocking Defaults
class does not work correctly. Because default values for method arguments are compiled as hidden methods in the same class, mock[Defaults]
returns an object in which these hidden methods return null
instead of None
, so this test fails:
class Test extends FreeSpec with ShouldMatchers with MockitoSugar {
"Defaults" - {
"should be called with default argument" in {
val d = mock[Defaults]
d.doSomething("abcd")
verify(d).doSomething("abcd", None)
}
}
}
The error:
Argument(s) are different! Wanted:
defaults.doSomething("abcd", None);
-> at defaults.Test$$anonfun$1$$anonfun$apply$mcV$sp$1.apply$mcV$sp(Test.scala:14)
Actual invocation has different arguments:
defaults.doSomething("abcd", null);
-> at defaults.Test$$anonfun$1$$anonfun$apply$mcV$sp$1.apply$mcV$sp(Test.scala:12)
The reason of this is clear, but is there a sensible workaround? The only one I see is to use spy()
instead of mock()
, but my mocked class contains a lot of methods which I will have to mock explicitly in this case, and I don't want it.
To check if a method was called on a mocked object you can use the Mockito. verify method: Mockito. verify(someMock).
eq(obj) checks that the argument equals obj according to its equals method. This is also the behavior if you pass in real values without using matchers. Note that unless equals is overridden, you'll see the default Object. equals implementation, which would have the same behavior as same(obj) .
Mockito allows us to create mock objects and stub the behavior for our test cases. We usually mock the behavior using when() and thenReturn() on the mock object.
This is related with 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$number where 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, an example of the “compiled” version would be something like this (note that this is not exactly what the compiler does, but it works for educational purposes)
class Foo {
def bar(noDefault: String, default: String = "default value"): String
}
val aMock = mock[Foo]
aMock.bar("I'm not gonna pass the second argument")
The last line would be compiled as
aMock.bar("I'm not gonna pass the second argument", aMock.bar$default$1)
Now, because we are making the call to bar$default$1
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…
In order to solve this some changes have to be made so mockito actually calls the real $default$
methods and so the replacement is done correctly
This work has been done in mockito-scala, so by migrating to that library you'll get a solution for this and many other problems that can be found when mockito is used in Scala
Disclaimer: I'm a developer of mockito-scala
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With