Home > Enterprise >  sender() results in deadLetters for implementation but not in unit tests
sender() results in deadLetters for implementation but not in unit tests

Time:09-17

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
  }
}
  • Related