玩! scala 和 Akka:如何测试演员 A 是否向演员 B 发送了消息?

Play! scala and Akka: how to test if an actor A sent a message to an actor B?

我想测试一个演员A在收到消息后向演员B发送消息。

我正在使用 Play! 2.5,我使用工厂,因为我需要在演员中注入一些我的 类 和 wSClient 之类的东西。

演员A长得像:

object ActorA {
  trait Factory {
    def apply(ec: ExecutionContext, actorBRef: ActorRef): Actor
  }
}

class ActorA @Inject()(implicit val ec: ExecutionContext,
                       @Named("actor-b") actorBRef: ActorRef)
    extends Actor with ActorLogging with InjectedActorSupport {

  override def receive: Receive = {
    case i: Long =>
      log info s"received $i"
      actorBRef ! (i+1)
}

而演员B就更简单了:

object ActorB {
  trait Factory {
    def apply(): Actor
  }
}

class ActorB extends Actor with ActorLogging {

  override def receive: Receive = {
    case _ =>
      log error "B received an unhandled message"
  }
}

但是我的测试没有通过,据说预期的消息没有到达,我在测试中超时(但是演员B很好地记录了)所以问题来自测试(可能还有探测器)。

这是测试:

  val actorBProbe = TestProbe()
  lazy val appBuilder = new GuiceApplicationBuilder().in(Mode.Test)
  lazy val injector = appBuilder.injector()
  lazy val factory = injector.instanceOf[ActorA.Factory]
  lazy val ec = scala.concurrent.ExecutionContext.Implicits.global
  lazy val factoryProps = Props(factory(ec, actorBProbe.ref))
  val ActorARef = TestActorRef[ActorA](factoryProps)

  "Actor B" must {

    "received a message from actor A" in {
      ActorARef ! 5L

      actorBProbe.expectMsg(6L)
    }
  }

我还创建了一个最小的Play!使用上述代码的应用程序可用 here.

在您的测试中,actorBProbe 不是传递给 ActorA 构造函数(ref ActorARef 的)的 ActorB 引用。真正发生的是 Guice 创建了一个不同的 ActorB(名为 actor-b),并将其 ref 传递给 ActorA(属于 ref ActorARef)构造函数。

测试以 ActorB actor-b 接收 6L 结束(如日志中所示)。而 actorBProbe 什么也没收到。

混淆实际上来自于将 Guice 生命周期与 Actors 混合在一起。根据我的经验,它造成的痛苦超出了我的承受能力。

为了证明,只需打印 ActorRef 的哈希码,您就会发现它们是不同的。图解如下:

val actorBProbe = TestProbe()
println("actorBProbe with ref hash: " + actorBProbe.ref.hashCode())

并且,

class ActorA ... {
  override def preStart =
    log error "preStart actorBRef: " + actorBRef.hashCode()

  // ...
}

事实上,即使ActorA里面的ec和测试代码中的ec也不一样

以下是 "force" 测试通过的方法,同时证明 actorBProbe 并未真正被 ActorB 使用。

而不是依赖 Guice 到 "wire in" ActorB,我们告诉 Guice 通过用 @Assisted 替换 @Named("actor-b") 来让它独立,就像这样,

import ...
import com.google.inject.assistedinject.Assisted

class ActorA @Inject()(...
  /*@Named("actor-b")*/ @Assisted actorBRef: ActorRef)
...

重新运行测试,它会通过。但这可能不是您想开始的。