ScalaTest 无法验证 Future 中的模拟函数调用

标签 scala mockito future scalatest

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

每次都失败并出现以下错误:
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)

我已经测试过它可以在没有 Future 的情况下工作。

最令我惊讶的是,如果我在 Future 块中也包含一个打印语句,它每次都会成功,如下所示:
Future {
  mockFunction()
  println("test")
}

知道问题是什么以及为什么打印声明在这里很重要吗?

我正在使用:
  • 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 在两种情况下的输出,看看它在哪里被调用:

    给定的

    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 调用方式的不同
    Future.apply({$anonfun(mockFunction) ...
    Future.apply(mockFunction ...
    

    在第一种情况下,它作为参数传递给 $anonfun,它确实像这样调用 apply$mcV$sp():
    mockFunction$1.apply$mcV$sp();
    

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

    使用 Future.successful { mockFunction() } 似乎可以让它工作,我们看到 apply$mcV$sp() 被按需调用
        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
    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
    <specialized> def apply$mcV$sp(): Unit = {
      Function0.this.apply();
      ()
    };
    

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

    关于ScalaTest 无法验证 Future 中的模拟函数调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56398817/

    相关文章:

    Scala将类型参数传递给对象

    java - Mockito spy 测试

    java - 为单元测试创​​建对象 MockHttpServletResponse 时出错

    Python:WAITING所有 `concurrent.futures.ThreadPoolExecutor` 的 future

    c++ - 这个围绕 std::future::then 的便利包装器有名称吗?

    java - 如何在 Java 中使用带有 WebClient 的 Vertx 路由器中的 future

    azure - Spark 3.3(scala)中UDF函数的问题

    scala - 我可以检查 Scala 中是否已计算惰性 val 吗?

    scala - 如何在 spark-sql 中使用 "not rlike"?

    java - Mockito 显示与 mock 的交互为 0