Reputation: 193
Assume I have the following trait, with a single method that receives a call-by-name parameter:
trait Client {
def compute(value: => String): String
}
Also, assume I have the following function:
final def getValue: String = {
"value"
}
Now let's say I'm trying to Mock this class using Mockito (org.specs2.mock.Mockito), the following way:
val client: Client = mock[Client]
client.compute(getValue) returns "result"
The problem is that when the mocked method is invoked, it doesn't return the expected value:
client.compute(getValue) mustEqual "result" // fails. returns null
As you can see, I'm using this parameter actually as a function I send to the method (a bit like a Supplier). I don't understand why the Mocking doesn't work. I cannot write my unit tests as long as I cannot control what client.compute(..) returns.
Help is much appreciated.
Upvotes: 1
Views: 2519
Reputation: 23788
Call-by-name parameters are actually compiled into something like this:
trait Client {
def compute(valueFunction => Function0[String]): String
}
and the call is converted into something like this
client.compute(() => { getValue() })
or putting it more explicitly:
client.compute(new Funciton0[String]{ def apply():String = { getValue() }})
So Mockito returns
doesn't work because in two calls of the compute
the parameter being passed is actually two different Function0
objects. And because equals
is not overridden for Function0
, they don't match.
The trick to work this around is to first explicitly convert your getValue
method into a local Function0[String]
variable. This seems to be enough to make two client.compute
calls generate identical objects for Mockito to work. So you may try to use following code:
import org.specs2._
import org.specs2.mock.Mockito
class Specs2Test extends Specification with Mockito {
override def is =
s2"""
this works $good
this doesn't $bad
"""
final def getValue: String = {
"value"
}
def good = {
val client: Client = mock[Client]
val f: Function0[String] = getValue _
client.compute(f()) returns "result"
client.compute(f()) mustEqual "result"
}
def bad = {
val client: Client = mock[Client]
client.compute(getValue) returns "result"
client.compute(getValue) mustEqual "result" // fails. returns null
}
}
Update
If what you actually test is not client.compute
but some other method in Java that inside calls client.compute
and you want to mock that call, I don't know how to help you preserving exact semantics without rewriting at least some of your code. Probably the simplest thing I can think of is to use Funciton0
in the signature explicitly and then use Matchers.any
such as
trait Client {
def compute(value: () => String): String
}
and then
def usingMatchAny = {
val client: Client = mock[Client]
client.compute(ArgumentMatchers.any()) returns "result"
// actually here you call the method that uses client.compute call
client.compute(getValue _) mustEqual "result"
}
Upvotes: 2
Reputation: 1651
The problem with the Mock is related to the fact that you are not explicitly declaring the return type of the compute
method in the Client
trait and given the fact that there is no body, it's inferred to be Unit
.
So when you try to define the mock behaviour with
client.compute(getValue) returns "result"
you are not defining the behaviour for that method. You have to explicitly declare the return type for a public method
Upvotes: 0