I'm working with Akka classic actors (running in Play framework) and am running into a problem with the sender()
method. When I build and run the code (using sbt run
), I can see that sender()
resolves to deadLetters
actor. However, when running unit tests for that actor, sender()
resolves to the correct ActorRef (even though it shouldn't).
The sender()
is being invoked from within a closing future which is why I'm seeing the deadLetters
when running the actual implementation. However, for some reason, I'm not seeing the deadLetters
in the unit tests. Any ideas to why there is a difference of behavior between the unittests and running instance?
Some sample code to give an idea of the code structure
class MyActor extends Actor {
private def throwError() = {
// THIS IS WHERE sender() BEHAVIOR DIFFERENTIATES BETWEEN IMPLEMENTATION AND UNITTESTS
sender() ! MyErrorMessage()
throw new Exception()
}
private def myFutureMethod(args: SomeType): Future[Done] = {
for {
Some logic here...
} yield {
if (some logic check...)
throwError()
else
Done
}
}
override def stepReceive = {
case MyMessage(args) =>
myFutureMethod(args)
}
}
Some sample code to give an idea of the unittest structure
"My failure test case" in {
val testProbe = TestProbe()
myActorImpl.tell(MyMessage(args), testProbe)
// testProbe SHOULD BE NOT BE RECEIVING A MESSAGE BASED ON THE ABOVE
// IMPLEMENTATION, BUT IT DOES.
testProbe.expectNoMessage()
}
If anyone has some tips or ideas for debugging, that would be awesome. Thanks.
CodePudding user response:
Calling sender
in code executed as part of a Future
within an actor is non-deterministic (viz. a race condition) because your MyActor
can move on to processing another message before the code in the Future
executes, and if it has since then, sender
will have changed, potentially to deadLetters
.
In your test, if myActorImpl
hasn't processed a message, sender
will still point to the test probe.
Assuming you want the message to actually be sent to the sender, you need to capture it before handing it to the Future
, along these lines
class MyActor extends Actor {
private def throwError(target: ActorRef) = {
// uses the captured sender, no race condition
target ! MyErrorMessage()
throw new Exception()
}
private def myFutureMethod(args: SomeType, sender: ActorRef): Future[Done] = {
for {
Some logic here...
} yield {
if (some logic check...)
throwError(sender)
else
Done
}
}
override def stepReceive = {
case MyMessage(args) =>
myFutureMethod(args, sender) // captures the sender reference
}
}