Understanding Testing of Akka Actors

Reading Time: 2 minutes

We may sometimes say the testing situation in Akka is a little confusing. In this blog you’ll learn about how the actors are tested in Akka. In Akka generally we have two kinds of tests, synchronous and asynchronous which some people term as ‘unit‘ and ‘integration‘ tests.

  • Unit tests‘ are synchronous, you directly test the receive method without requiring an actor system, etc.
  • Integration tests‘ are asynchronous, and generally test more than one actor working together. This is where you inherit TestKit, instantiate TestProbes to act as your talkers. 

Akka comes with a dedicated module akka-testkit for supporting tests. To use Actor Testkit in our project we need to add the following dependency,

val AkkaVersion = "2.6.10"
libraryDependencies += "com.typesafe.akka" %% "akka-testkit" % AkkaVersion % Test

Asynchronous Testing

Testkit allows you to test your actors in a controlled but realistic environment. The test kit class, which gives us the basic tools we need to work with actors. This includes access to the actor system, as well as helper methods for dealing with responses from actors under test. The minimal setup consists of the test procedure, the actor under test, and an actor receiving replies.

Example of Asynchronous Testing in Akka:

Class MyTest()
    extends TestKit(ActorSystem("MyTest"))
    with ImplicitSender
    with AnyWordSpecLike
    with Matchers
    with BeforeAndAfterAll {
 
  override def afterAll(): Unit = {
    TestKit.shutdownActorSystem(system)
  }
 
  "My actor" must {
 
    "send back messages without changing anything" in {
      val echo = system.actorOf(TestActors.echoActorProps)
      echo ! "hello world"
      expectMsg("hello world")
    }
 
  }

The TestKit contains an actor named testActor which is the entry point for messages to be examined with the various expectMsg assertions shown above. When mixing in the trait ImplicitSender this test actor is implicitly used as sender reference when dispatching messages from the test procedure.

The above mentioned expectMsg is not the only method used for assertions of received messages. There more like expectMsgAnyOf(“hello”, “world”), expectMsgAllOf(“hello”, “world”) etc.

Synchronous Testing

Testing the business logic inside Actor classes can be divide into two parts. First is that each atomic operation must work in isolation. And second is sequences of incoming events must be processed correctly, even in the presence of some possible variability in the ordering of events. The former is the primary use case for single-threaded unit testing, while the latter can only be verified in integration tests.

Normally, the ActorRef shields the underlying Actor instance from the outside, the only communications channel is the actor’s mailbox. This restriction is an impediment to unit testing, which led to the concept of the TestActorRef

This special type of reference design specifically for test purposes and allows access to the actor in two ways

  1. Either by obtaining a reference to the underlying actor instance.
  2. Or by invoking or querying the actor’s behavior (receive).
import akka.testkit.TestActorRef
import akka.pattern.ask
 
val actorRef = TestActorRef(new MyActor)
// hypothetical message returning a '42' answer
val future = actorRef ? Say42
future.futureValue should be(42)

To summarize in short, the TestActorRef overwrites two fields. Firstly, it sets the dispatcher to CallingThreadDispatcher.global. And secondly, it sets the receiveTimeout to None.

Despite of this, akka highly recommends to stick to traditional behavioral testing. Behavioral Testing is where we use message to ask the Actor to reply with the state we want to run assertions against instead of using TestActorRef whenever possible.

That’s all for this post.

Reference:

  1. https://doc.akka.io/docs/akka/current/testing.html

Knoldus-blog-footer-image

Leave a Reply