ScalaTest无法验证Future内部的模拟函数调用 [英] ScalaTest can't verify mock function invocations inside Future

查看:114
本文介绍了ScalaTest无法验证Future内部的模拟函数调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将Scala的Future与ScalaTest和Mockito一起使用,但是通过一个非常简单的测试用例,我无法验证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.

让我最惊讶的是,如果我还在Future块中也包含一个print语句,它每次都会成功,如下所示:

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?

我正在使用:

  • Scala 2.12.8
  • scalatest_2.12 3.0.5
  • mockito 2.27.0
  • 使用Scala插件2019.1.8在IntelliJ 2019.1.3中运行测试

推荐答案

该错误表明未调用apply$mcV$sp(),因此让我们分别比较这两种情况下-Xprint:jvm的输出,以查看它的调用位置:

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:

给出

Future {
  mockFunction()
  println("test")
}

-Xprint:jvm的输出是

    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()
    };

有空

Future {
  mockFunction()
}

-Xprint:jvm的输出是

    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()
    };

请注意在mockFunction调用方式上的区别

Note the difference in how mockFunction gets invoked

Future.apply({$anonfun(mockFunction) ...
Future.apply(mockFunction ...

在第一种情况下,它作为参数传递给$anonfun,而确实如此调用apply$mcV$sp():

In the first case it passed as argument to $anonfun which indeed invokes apply$mcV$sp() like so:

mockFunction$1.apply$mcV$sp();

在第二种情况下,找不到apply$mcV$sp()的调用.

whilst in the second case invocation of apply$mcV$sp() is nowhere to be found.

使用Future.successful { mockFunction() }似乎可以使其正常工作,并且我们看到apply$mcV$sp()被按需调用

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()
    };

apply$mcV$sp首先来自哪里?检查 Function0

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>"
}

我们看到@specialized(Specializable.Primitives)结果

  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 = {
      ()
    }
  };

我们在其中看到apply$mcV$sp的地方依次称为实际apply

where we see apply$mcV$sp in turn calls actual apply

<specialized> def apply$mcV$sp(): Unit = {
  Function0.this.apply();
  ()
};

这些似乎是问题的一部分,但是我没有足够的知识来将它们组合在一起.在我看来,Future(mockFunction())应该可以正常工作,所以我们需要一个更精通的人来解释它.在此之前,请尝试Future.successful作为解决方法.

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.

这篇关于ScalaTest无法验证Future内部的模拟函数调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆