Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I test an Akka actor that sends a message to another actor?

I'm using ScalaTest with the Akka TestKit to write unit and integration tests for an actor I've coded to simply send a message to another actor without mutating any internal state. Take this for example:

class MyActor extends Actor {
  val anotherActor = context.actorOf(Props[AnotherActor])
  def receive: Receive = {
    case MyMessage => anotherActor ! AnotherMessage
  }
}

I want to write a test that confirms that anotherActor processed AnotherMessage as a consequence of MyActor processing MyMessage. The classic example is to use TestActorRef to get at the underlying actor and check for some internal state that should have been affected upon message receipt like so:

val testActorRef = TestActorRef(new MyActor)
"MyActor" should "change some internal state when it receives MyMessage" in {
  testActorRef ! MyMessage
  testActorRef.underlyingActor.someState shouldBe "altered"
}

But in my case I don't care about such state. In fact, I want to avoid holding any such state. TestProbe wasn't quite what I was looking for either since you still have to register aTestProbe.ref with the actor under test. For the most part, I've looked at all the examples in the Akka documentation on testing (http://doc.akka.io/docs/akka/snapshot/scala/testing.html) but haven't found anything suitable.

like image 823
nmurthy Avatar asked Mar 26 '15 02:03

nmurthy


People also ask

Can an Akka actor stop other actors?

In Akka, you can stop Actors by invoking the stop() method of either ActorContext or ActorSystem class. ActorContext is used to stop child actor and ActorSystem is used to stop top level Actor.

Are Akka actors single threaded?

Behind the scenes Akka will run sets of actors on sets of real threads, where typically many actors share one thread, and subsequent invocations of one actor may end up being processed on different threads.

What happens when an actor fails in Akka?

When an actor throws an unexpected exception, a failure, while processing a message or during initialization, the actor will by default be stopped.


1 Answers

There's probably a few ways to do this, I'll show you one that works when we have something similar to test. I still think TestActorRef, TestKit and TestProbe are the way to go. Considering the following structure:

case object MyMessage
case object AnotherMessage

class MyActor extends Actor {
  val anotherActor = createAnother
  def receive: Receive = {
    case MyMessage => anotherActor ! AnotherMessage
  }

  def createAnother = context.actorOf(Props[AnotherActor])
}

class AnotherActor extends Actor{
  def receive = {
    case _ =>
  }
}

The issue is that you have an actor instance creating a child actor and as part of your test you need to make sure that child gets a message even though you in your test don't have any control in the creation of that child. When we have this situation, we do something simple like the following (done using specs2, but should be able to create something similar in ScalaTest):

import akka.actor._
import akka.testkit._
import org.specs2.mutable.SpecificationLike
import org.specs2.runner.JUnitRunner
import org.junit.runner.RunWith

class MessageSendingSpec extends TestKit(ActorSystem("test")) with SpecificationLike{

  val probe = TestProbe()
  val myActor = TestActorRef(new MyActor{
    override def createAnother = probe.ref
  })


  "Sending MyMessage to an instance of MyActor" should{
    "pass AnotherMessage to the child AnotherActor" in {
      myActor ! MyMessage
      probe.expectMsg(AnotherMessage)
      success
    }
  }
}

The key there is that when creating the actor to test, I override the method that creates the child in order to supply my probe. It's crude, but also simple and effective.

like image 194
cmbaxter Avatar answered Sep 29 '22 12:09

cmbaxter