Akka · Testing · Tutorial

How to test Actors with Akka TestKit and Spec2

Actors are a really powerful tool to handle concurrency thanks to their message-based model. However, they can be tricky to test: sending and processing messages is done asynchronously. Moreover, their status is hidden internally and it cannot be easily accessed to make assertions on it.

The Akka Team has created a library, called akka-testkit, to simplify unit tests on actors. This article provides an overview of the main features of this library and how it can be used to test our lovely actors.

Single Threaded Tests

If our actor is particularly simple, a single threaded test may be enough. Thanks to TestActorRef, we are able to access the actor internal status and make assertions on it.

For example, we have built an actor that memorises all the received messages starting with ‘A’:

import akka.actor.Actor

object MessageFilteringActorProtocol {
  case class SimpleMessage(text: String)
}

class MessageFilteringActor extends Actor {
  import MessageFilteringActorProtocol._
  
  var messages = Vector[String]()
  
  // what the actor state is
  def state = messages
  
  // the actor behaviour when receiving an object
  def receive = {
    case SimpleMessage(text) if text startsWith "A" =>
      messages = messages :+ text
  }

}

Let’s build a test for our actor:

import akka.testkit.TestKit
import akka.actor.ActorSystem
import org.specs2.mutable.SpecificationLike

class MessageFilteringActorSpec extends TestKit(ActorSystem())
  with SpecificationLike {
  
  import MessageFilteringActorProtocol._
  
  val actor = TestActorRef[MessageFilteringActor]
  "A Message Filtering Actor" should {
    
    "save only messages that starts with 'A'" in {
      actor ! SimpleMessage("A message to remember")
      actor ! SimpleMessage("This message should not be saved")
      actor ! SimpleMessage("Another message for you")
      actor.underlyingActor.state.length mustEqual 2
    }
    
  }
}

Multi Threaded Testing

Unfortunately, single threaded unit testing is not always sufficient with more complex scenarios. To perform multi threaded tests, we have access to the TestProbe class that offers useful methods to wait and analyse the status and interaction with our actor. Some of the most common methods are the following:
expectMsg: it receives a message that is equal to the provided one
expectNoMsg: it receives no message
receiveWhile: it receives messages until the condition is respected or the time out is reached.
A complete list of all the methods offered by the TestProbe class can be found here.

Although the TestProbe class is quite powerful, it may require some changes in the actor code itself to make it more testable: we need to make sure that the actor is sending messages/information to our TestProbe class so that it can perform assertions on them.

A quite common approach is to create ad hoc messages for test purposes. For example, let’s assume we would like to know the internal status of our actor in a multi-threaded testing context. Moreover, we can have an optional listener to help us testing side effects.

An example on how to use these different approaches is as follows. Our BucketCounterActor prints the label on a bucket and it accumulates all the quantities received so far:

import akka.actor.Actor

object BucketCounterActorProtocol {
  case class Bucket(label: String, quantity: Int)
}

class BucketCounterActor extends Actor {
  import BucketCounterActorProtocol._
  
  var counter = 0
  
  def receive = {
    case Bucket(label, quantity) =>
      counter += quantity
      print(label)
  }

}

Let’s add some ad hoc code to our actor for test purposes:

import akka.actor.{ActorRef, Actor}

object BucketCounterActorProtocol {
  case class Bucket(label: String, quantity: Int)
  
  // a new message to expose the internal status of the actor 
  case class GetCounter(receiver: ActorRef)
}

// adding an optional listener to the class
class BucketCounterActor(listener: Option[ActorRef] = None) extends Actor {
  import BucketCounterActorProtocol._
  
  var counter = 0
  
  def receive = {
    case Bucket(label, quantity) =>
      counter = counter + quantity
      print(label)
      // informing the listener of the side effect
      listener.map(_ ! label)
    
    // logic to expose internal status
    case GetCounter(receiver) => receiver ! counter
  }

}

Thanks to the code we just added, testing our actor is now going to be really easy:

class BucketCounterActorSpec extends TestKit(ActorSystem()) with SpecificationLike {
  import BucketCounterActorProtocol._
  
  "A Bucket Counter Actor" should {
    
    val actorProps = Props(new BucketCounterActor(Some(testActor)))
    val actor = system.actorOf(actorProps, "actor-to-test")
    
    val firstBucket = Bucket("Yo, I am a bucket", 1)
    val secondBucket = Bucket("I am another bucket", 9)

    "print out the name of the received buckets" in {
      actor ! firstBucket
      expectMsg(firstBucket.label)
      actor ! secondBucket
      expectMsg(secondBucket.label)
      success
    }
    
    "accumulate the quantity of buckets received" in {
      actor ! GetCounter(testActor)
      expectMsg(10)
      success
    }
  }

Summary

Akka actors are a powerful tool to build concurrent systems. This article has provided different examples on how actors can be tested thanks to the akka-testkit library, using both single and multi threaded approaches.

2 thoughts on “How to test Actors with Akka TestKit and Spec2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s