玩! 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)
...
重新运行测试,它会通过。但这可能不是您想开始的。
我想测试一个演员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)
...
重新运行测试,它会通过。但这可能不是您想开始的。