How to test that Akka actor was created in Scala
Asked Answered
A

2

10

I'm trying to write a test that will verify that my actor below is creating a heartBeatExpireWorker and a heartBeatAccepter, but I have no idea how to do it.

First I was thinking I could use Mockhito mock or a spy in place of context and then verify that I called actorOf, but I can't figure out a way to inject the context without breaking the Akka testing framework.

Then, I was thinking that I could send an Identify message to the workers to verify that they exist. But it occurred to me that that wouldn't work either because the Akka TestKit doesn't seem to create children actors of an actor under test. It can only take in Testprobes that can stand in for neighboring actors.

class HeartBeatPumpWorker(chatService: ChatService, target: HeartBeatMessageCmd) extends Actor with ActorLogging with
WorkersReference {

  val heartBeatInterval = chatService.getHeartBeatInterval

  val tick = context.system.scheduler.schedule(0 millis, heartBeatInterval millis, self, SendHeartBeat(target))

  override def postStop() = tick.cancel()

  def receive = {
    case SendHeartBeat(command: HeartBeatMessageCmd) =>
      log.debug("Sending heartbeat")
      //Send heartbeat to GWT
      val userTarget = NetworkWorker.buildEventUserTarget(command.getEventCode, command.getUser)

      val uuid: String = UUID.randomUUID().toString
      val freshCommand = new HeartBeatMessageCmd(command.getUser, command.getEventCode, uuid, command.getUserSession)
      networkWorker ! NetworkBroadcast(userTarget, freshCommand)

      val heartBeatId: String = freshCommand.getUuid
      //create expirer
      val heartBeatExpireWorkerRef = context.actorOf(HeartBeatExpireWorker.props(chatService, freshCommand),
        HeartBeatExpireWorker.name(heartBeatId))
      val heartBeatAccepterRef = context
        .actorOf(HeartBeatAcceptWorker.props(chatService, freshCommand), HeartBeatAcceptWorker.name(heartBeatId))

      //record heartbeat
        chatService.saveSentHeartbeat(heartBeatId, freshCommand.getUserSession, freshCommand.getEventCode,
          freshCommand.getUser,
        freshCommand.getTimeCmdGenerated)
    case _ =>
      log.error("Pumper received unknown message.  This shouldn't happen " + sender.path.toString)
      self ! PoisonPill
  }

}


object HeartBeatPumpWorker {
  def name(eventCode: String, user: String, sessionId: String) = f"HeartBeatPumpWorker-$eventCode-$user-$sessionId"

  def path(eventCode: String, user: String, sessionId: String) : String = {
    EventWorker.Path + "/" + name(eventCode, user, sessionId)
  }

  def props(chatService: ChatService, heartBeatMsgCmd: HeartBeatMessageCmd) = {
    Props(classOf[HeartBeatPumpWorker], chatService, heartBeatMsgCmd)
  }
}
Abase answered 3/9, 2013 at 23:7 Comment(0)
T
3

Inject Props for the children (e.g. HeartBeatAcceptWorker.props) in the constructor of the parent HeartBeatPumpWorker. Pass any Props you want from the test. Let the parent instantiate the children via provided Props. Interact with the children. The last part is dependent on your data flow. For instance if the parent shields you from the children, but delegates messages to them, send the message to the parent. If children talk to each other use test probes or something similar.

Trustee answered 4/9, 2013 at 7:42 Comment(4)
I'm not sure I follow. I need to have the Props defined in the receive method because I'm using the incoming argument to define the path for my newly generated heartBeatExpireWorkerRef and heartBeatAccepterRef. So I don't think I can have the props injected.Abase
Well then you need to inject them via the message. As last resort you could inject the name for the child via the message and then look the child up with ActorSelection. Does that help?Trustee
That sounds good, but does the Akka TestKit allow an actor to make child actors? All the examples I've found so far only allow an actor to interact with test probes. I haven't found any example tests where an actor under test creates child actors. Do you know where I could find an example test like that?Abase
I'm not sure I understand what you mean. The Actor under test is just an Actor. Take a look at the fault tolerance chapter, maybe it'll help.Trustee
R
16

The technique I'm currently using is to intercept actor creation and create TestProbes. In my actors I mix in a separate ActorMaker trait:

trait ActorMaker { this: Actor =>
  def makeActor(props: Props) = context.actorOf(props)
}

And use it in MyActor extends Actor with ActorMaker instead of context.actorOf.

For tests I have a TestProbeMaker that captures all created actors and their props:

trait TestProbeMaker { this: Actor =>
  val probes = ListBuffer.empty[(Props, TestProbe)]
  def makeActor(props: Props) = { val probe = TestProbe()
    probes += (props -> probe)
    probe.ref
  }
}

And I mix it in during tests

val actorUnderTest = TestActorRef(Props(new MyActor with TestProbeMaker))

That way I can assert exactly what actors are created. I can also use probe.expectMsg to assert that messages are sent to those created actors.

To access the probes use actorUnderTest.underlyingActor.asInstanceOf[TestProbeMaker]

Rickets answered 18/9, 2013 at 16:1 Comment(2)
Hi, this seems nice. But it's not clear how you can get access to the TestProbe. In your test class how are you accessing the 'probes' list given that it's a field mixed in when your actor under test is created? I don't suppose you have a code example?Disgust
@Disgust you can get to the list of probes using actorUnderTest.underlyingActor by casting it to TestProbeMaker.Rickets
T
3

Inject Props for the children (e.g. HeartBeatAcceptWorker.props) in the constructor of the parent HeartBeatPumpWorker. Pass any Props you want from the test. Let the parent instantiate the children via provided Props. Interact with the children. The last part is dependent on your data flow. For instance if the parent shields you from the children, but delegates messages to them, send the message to the parent. If children talk to each other use test probes or something similar.

Trustee answered 4/9, 2013 at 7:42 Comment(4)
I'm not sure I follow. I need to have the Props defined in the receive method because I'm using the incoming argument to define the path for my newly generated heartBeatExpireWorkerRef and heartBeatAccepterRef. So I don't think I can have the props injected.Abase
Well then you need to inject them via the message. As last resort you could inject the name for the child via the message and then look the child up with ActorSelection. Does that help?Trustee
That sounds good, but does the Akka TestKit allow an actor to make child actors? All the examples I've found so far only allow an actor to interact with test probes. I haven't found any example tests where an actor under test creates child actors. Do you know where I could find an example test like that?Abase
I'm not sure I understand what you mean. The Actor under test is just an Actor. Take a look at the fault tolerance chapter, maybe it'll help.Trustee

© 2022 - 2024 — McMap. All rights reserved.