Reputation: 21017
I'm trying to mock a method call that takes a call-by-name argument:
import org.scalatest.WordSpec
import org.scalatest.mock.MockitoSugar
import org.mockito.Mockito._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
trait Collaborator {
def doSomething(t: => Thing)
}
trait Thing
@RunWith(classOf[JUnitRunner])
class Test extends WordSpec with MockitoSugar {
"The subject under test" should {
"call the collaborator" in {
// setup
val m = mock[Collaborator]
val t = mock[Thing]
// test code: this would actually be invoked by the SUT
m.doSomething(t)
// verify the call
verify(m).doSomething(t)
}
}
}
I'm primarily interested in Mockito since that's what I'm using, but I'd be interested to see whether any of the major mock frameworks is capable of this kind of testing. The Test fails at runtime on the verify
line, with an error like
Argument(s) are different! Wanted: collaborator.doSomething( ($anonfun$apply$3) <function> ); -> at Test$$anonfun$1$$anonfun$apply$1.apply(Test.scala:27) Actual invocation has different arguments: collaborator.doSomething( ($anonfun$apply$2) <function> ); -> at Test$$anonfun$1$$anonfun$apply$1.apply(Test.scala:24)
If I'm understanding the situation correctly, the compiler is implicitly wrapping t
in a nullary function that returns t
. The mock framework is then comparing that function to the one produced in the test code, which is equivalent but not equals()
.
My case is a relatively simple version of the problem, but I think this would be an issue with any higher-order function.
Upvotes: 23
Views: 17276
Reputation: 15557
You can try specs2. In specs2, we "hijack" the Mockito Invocation
class to account for byname parameters:
trait ByName { def call(i: =>Int) = i }
val byname = mock[ByName]
byname.call(10)
there was one(byname).call(10)
Upvotes: 2
Reputation: 837
This problem seems to be specific to by-name invocations because in regular higher order functions you can match against the explicit FunctionX object:
verify(collaborator).somethingElse(any(Function2[String, Thing]))
in the by-name case the wrapping of the argument into a Function0 is done implicitly, and Alexey's answer shows how to invoke the mock with an explicit parameter.
You could write something akin to your own verify which would apply arguments captured by mockito.
Mockito internally records invocation and their arguments with e.g.: http://code.google.com/p/mockito/source/browse/trunk/src/org/mockito/internal/matchers/CapturingMatcher.java
Upvotes: 1
Reputation: 9447
This looks ugly, but hopefully it can help you to find good solution:
import org.scalatest.mock.MockitoSugar
import org.mockito.Mockito._
trait Collaborator {
def doSomething(t: => Thing)
}
trait Thing
new MockitoSugar {
// setup
val m = mock[Collaborator]
val t = mock[Thing]
m.doSomething(t)
classOf[Collaborator].getMethod("doSomething", classOf[Function0[_]]).invoke(
verify(m),
new Function0[Thing] {
def apply() = null
override def equals(o: Any): Boolean = t == o.asInstanceOf[Function0[Thing]].apply()
})
}
Upvotes: 11