Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ScalaTest can't verify mock function invocations inside Future

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
like image 665
Søren Avatar asked May 31 '19 16:05

Søren


2 Answers

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.

like image 102
Mario Galic Avatar answered Nov 16 '22 15:11

Mario Galic


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.

like image 1
Bruno Avatar answered Nov 16 '22 15:11

Bruno