I'm trying to use Scala's Future together with ScalaTest and Mockito, but with a very simple test case, I'm not able to verify any of the invocations on a mocked function inside the Future.
import org.mockito.Mockito.{timeout, verify}
import org.scalatest.FunSpec
import org.scalatest.mockito.MockitoSugar
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
class FutureTest extends FunSpec with MockitoSugar {
it("future test") {
val mockFunction = mock[() => Unit]
Future {
mockFunction()
}
verify(mockFunction, timeout(1000)).apply()
}
}
This fails everytime with the following error:
Wanted but not invoked:
function0.apply$mcV$sp();
-> at test.FutureTest.$anonfun$new$1(FutureTest.scala:18)
However, there was exactly 1 interaction with this mock:
function0.apply();
-> at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:658)
I have tested that it works without the Future.
Most surprising to me is that if I include a print statement inside the Future block as well, it succeeds every time, as such:
Future {
mockFunction()
println("test")
}
Any idea of what the issue is and why print statement matters here?
I'm using:
- Scala 2.12.8
- scalatest_2.12 3.0.5
- mockito 2.27.0
- running the tests inside IntelliJ 2019.1.3 with Scala plugin 2019.1.8
The error indicates
apply$mcV$sp()
is not being invoked, so let's try comparing the output of-Xprint:jvm
in both cases respectively to see where it is called:Given
the output of
-Xprint:jvm
iswhilst having
the output of
-Xprint:jvm
isNote the difference in how
mockFunction
gets invokedIn the first case it passed as argument to
$anonfun
which indeed invokesapply$mcV$sp()
like so:whilst in the second case invocation of
apply$mcV$sp()
is nowhere to be found.Using
Future.successful { mockFunction() }
seems to make it work, and we seeapply$mcV$sp()
being invoked as requiredWhere does
apply$mcV$sp
come from in the first place? ExaminingFunction0
we see
@specialized(Specializable.Primitives)
which results inwhere we see
apply$mcV$sp
in turn calls actualapply
These seem to be some of the pieces of the problem, however I do not have sufficient knowledge to put them together. To my mind
Future(mockFunction())
should work just fine, so we need someone more knowledgeable to explain it. Until then, tryFuture.successful
as a workaround.try below.
As @mario-galic correctly pointed out, this is due to synthetic methods generated by the compiler being called rather than the ones we (and Mockito) would expect.
I'm afraid there is no way to solve it with the Java version of Mockito as it is not aware of all the extra stuff the Scala compiler does.
mockito-scala 1.5.2 solves this issue for Scala 2.12 and 2.13 as it knows how to handle said synthetic methods appropriately. I'd recommend you replace mockito-core with it to avoid this and many other issues.