ScalaTest не может проверить вызовы фиктивных функций внутри Future - PullRequest
4 голосов
/ 31 мая 2019

Я пытаюсь использовать Scala's 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 {
  mockFunction()
  println("test")
}

Любая идея о том, что проблема и почему здесь важно утверждение print?

Я использую:

  • Scala 2.12.8
  • scalatest_2.12 3.0.5
  • mockito 2.27.0
  • запуск тестов внутри IntelliJ 2019.1.3 с плагином Scala 2019.1.8

Ответы [ 3 ]

3 голосов
/ 01 июня 2019

Ошибка указывает, что 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 в качестве обходного пути.

1 голос
/ 03 июня 2019

Как правильно заметил @ mario-galic, это связано с синтетическими методами, генерируемыми вызываемым компилятором, а не теми, которые мы (и Mockito) ожидали.

Боюсь, что нет никакого способарешить эту проблему с помощью Java-версии Mockito, поскольку он не знает обо всех дополнительных вещах, которые делает компилятор Scala.

mockito-scala 1.5.2 решает эту проблему для Scala 2.12 и 2.13поскольку он знает, как правильно обрабатывать указанные синтетические методы.Я бы порекомендовал вам заменить ядро ​​mockito, чтобы избежать этой и многих других проблем.

0 голосов
/ 31 мая 2019

попробуйте ниже.

val f: Future = Future {
      mockFunction()
 }

f onComplete {
   verify(mockFunction, timeout(1000)).apply()
}
...