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:
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
Future {
mockFunction()
println("test")
}
the output of -Xprint:jvm
is
final <static> <artifact> def $anonfun$new$2(mockFunction$1: Function0): Unit = {
mockFunction$1.apply$mcV$sp();
scala.Predef.println("test")
};
final <static> <artifact> def $anonfun$new$1($this: FutureTest): Unit = {
val mockFunction: Function0 = $this.mock((ClassTag.apply(classOf[scala.Function0]): scala.reflect.ClassTag)).$asInstanceOf[Function0]();
scala.concurrent.Future.apply({
$anonfun(mockFunction)
}, scala.concurrent.ExecutionContext$Implicits.global());
org.mockito.Mockito.verify(mockFunction, org.mockito.Mockito.timeout(1000L)).$asInstanceOf[Function0]().apply$mcV$sp()
};
whilst having
Future {
mockFunction()
}
the output of -Xprint:jvm
is
final <static> <artifact> def $anonfun$new$1($this: FutureTest): Unit = {
val mockFunction: Function0 = $this.mock((ClassTag.apply(classOf[scala.Function0]): scala.reflect.ClassTag)).$asInstanceOf[Function0]();
scala.concurrent.Future.apply(mockFunction, scala.concurrent.ExecutionContext$Implicits.global());
org.mockito.Mockito.verify(mockFunction, org.mockito.Mockito.timeout(1000L)).$asInstanceOf[Function0]().apply$mcV$sp()
};
Note the difference in how mockFunction
gets invoked
Future.apply({$anonfun(mockFunction) ...
Future.apply(mockFunction ...
In the first case it passed as argument to $anonfun
which indeed invokes apply$mcV$sp()
like so:
mockFunction$1.apply$mcV$sp();
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 see apply$mcV$sp()
being invoked as required
final <static> <artifact> def $anonfun$new$1($this: FutureTest): Unit = {
val mockFunction: Function0 = $this.mock((ClassTag.apply(classOf[scala.Function0]): scala.reflect.ClassTag)).$asInstanceOf[Function0]();
scala.concurrent.Future.successful({
mockFunction.apply$mcV$sp();
scala.runtime.BoxedUnit.UNIT
});
org.mockito.Mockito.verify(mockFunction, org.mockito.Mockito.timeout(1000L)).$asInstanceOf[Function0]().apply$mcV$sp()
};
Where does apply$mcV$sp
come from in the first place? Examining Function0
trait Function0[@specialized(Specializable.Primitives) +R] extends AnyRef { self =>
def apply(): R
override def toString() = "<function0>"
}
we see @specialized(Specializable.Primitives)
which results in
abstract trait Function0 extends Object { self: example.Fun =>
def apply(): Object;
override def toString(): String = "<function0>";
<specialized> def apply$mcZ$sp(): Boolean = scala.Boolean.unbox(Fun.this.apply());
<specialized> def apply$mcB$sp(): Byte = scala.Byte.unbox(Fun.this.apply());
<specialized> def apply$mcC$sp(): Char = scala.Char.unbox(Fun.this.apply());
<specialized> def apply$mcD$sp(): Double = scala.Double.unbox(Fun.this.apply());
<specialized> def apply$mcF$sp(): Float = scala.Float.unbox(Fun.this.apply());
<specialized> def apply$mcI$sp(): Int = scala.Int.unbox(Fun.this.apply());
<specialized> def apply$mcJ$sp(): Long = scala.Long.unbox(Fun.this.apply());
<specialized> def apply$mcS$sp(): Short = scala.Short.unbox(Fun.this.apply());
<specialized> def apply$mcV$sp(): Unit = {
Function0.this.apply();
()
};
def /*Fun*/$init$(): Unit = {
()
}
};
where we see apply$mcV$sp
in turn calls actual apply
<specialized> def apply$mcV$sp(): Unit = {
Function0.this.apply();
()
};
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, try Future.successful
as a workaround.
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.
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