Click here to Skip to main content
15,867,308 members
Articles
(untagged)

AKKA: TESTKIT

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
14 Sep 2016CPOL7 min read 7.5K   1  
I thought it would be a good idea to take a small detour and look at how you can test Akka actor systems.

So the journey continues, we have covered a fair bit of ground, but there is still a long way to go yet, with many exciting features of Akka yet to cover.

Bit before we get on to some of the more advanced stuff, I thought it would be a good idea to take a small detour and look at how you can test Akka actor systems.

TestKit

Akka comes with a completely separate module for testing which you MUST include. This can be obtained using the following SBT settings:

name := "Testing"
 
version := "1.0"
 
scalaVersion := "2.11.8"
 
libraryDependencies ++= Seq(
  "com.typesafe.akka"   %%    "akka-actor"    %   "2.4.8",
  "com.typesafe.akka"   %%    "akka-testkit"  %   "2.4.8"   %   "test",
  "org.scalatest"       %%    "scalatest"     %   "2.2.5"   %   "test"
)

I have chosen to use scalatest, but you could use other popular testing frameworks such as specs2.

Testing Actors

The following sub sections will outline how Akka allows the testing of actors.

The built in ‘testActor’

The official Akka testkit docs do a great job of explaining the testActor and why there is a need for a test actor.

Testing the business logic inside Actor classes can be divided into two parts: first, each atomic operation must work in isolation, then 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 inception of the TestActorRef.

It is by using this testActor that we are able to test the individual operations of an actor under test.

You essentially instantiate the TestActorRef passing it the real actor you would like to test. The test actor then allows you to send messages which are forwarded to the contained real actor that you are attempting to test.

CallingThreadDispatcher/TestActorRef

As Akka is an asynchronous beast by nature, it uses the concept of Dispatchers to conduct the dispatching of messages. We have also seen that the message loop (receive) can be replaced with become/unbecome, all of which contributes to the overall behviour of the actor being quite hard to test.

Akka comes with a special actor called TestActorRef. Which is a special actor that comes with the Akka TestKit.

It should come as no surprise that this TestActorRef also makes use of a Dispatcher. But what makes this actor more suited to testing is that it uses a specialized testing Dispatcher, which makes testing the asynchronous code easier to test.

The specialized dispatcher is called CallingThreadDispatcher. As the name suggests, it uses the current thread to deal with the message dispatching.

This makes things easier that there is no doubt. As stated, you don’t really need to do anything other than use the Akka TestKit TestActorRef.

Anatomy Of An Actor Testkit Test

This is what a basic skeleton looks like when using the Akka TestKit (please note this is for ScalaTest).

import akka.actor.{Props, ActorSystem}
import akka.util.Timeout
import org.scalatest._
import akka.testkit.{ImplicitSender, TestKit, TestActorRef}
import scala.concurrent.duration._
import scala.concurrent.Await
import akka.pattern.ask
 
import scala.util.Success
 
class HelloActorTests
  extends TestKit(ActorSystem("MySpec"))
  with ImplicitSender
  with WordSpecLike
  with BeforeAndAfterAll
  with Matchers {
 
  override def afterAll {
    TestKit.shutdownActorSystem(system)
  } 
}

There are a couple of things to note there, so let's go through them.

  • Extending the akka TestKit trait. allows us to get all the good pre-canned assertions that allow us to assert facts about our actors.
  • Extending the akka ImplicitSender trait allows us to have an actual sender which would be set to the test suits self actor as the sender.

The Built In Assertions

The Akka TestKit comes with many useful assertions which you can see here:

expectMsg[T](d: Duration, msg: T): T
expectMsgPF[T](d: Duration)(pf: PartialFunction[Any, T]): T
expectMsgClass[T](d: Duration, c: Class[T]): T
expectMsgType[T: Manifest](d: Duration)
expectMsgAnyOf[T](d: Duration, obj: T*): T
expectMsgAnyClassOf[T](d: Duration, obj: Class[_ <: T]*): T
expectMsgAllOf[T](d: Duration, obj: T*): Seq[T]
expectMsgAllClassOf[T](d: Duration, c: Class[_ <: T]*): Seq[T]
expectMsgAllConformingOf[T](d: Duration, c: Class[_ <: T]*): Seq[T]
expectNoMsg(d: Duration)
receiveN(n: Int, d: Duration): Seq[AnyRef]
fishForMessage(max: Duration, hint: String)
    (pf: PartialFunction[Any, Boolean]): Any
receiveOne(d: Duration): AnyRef
receiveWhile[T](max: Duration, idle: Duration, messages: Int)
    (pf: PartialFunction[Any, T]): Seq[T]
awaitCond(p: => Boolean, max: Duration, interval: Duration)
awaitAssert(a: => Any, max: Duration, interval: Duration)
ignoreMsg(pf: PartialFunction[AnyRef, Boolean])
within[T](min: FiniteDuration, max: FiniteDuration)
    (f: ⇒ T): T

You can read more about these at the official documentation:

Demo: HelloActor That We Will Test

Lets assume we have this actor which we would like to test for the next 3 points:

import akka.actor.Actor

class HelloActor extends Actor {
  def receive = {
    case "hello" => sender ! "hello world"
    case _       => throw new IllegalArgumentException("bad juju")
  }
}

Sending Messages

So we have the HelloActor above, and we would like to send a message to it, and assert that the sender (the test suite testActor) gets a message back.

Here is how we might do that.

import akka.actor.{Props, ActorSystem}
import akka.util.Timeout
import org.scalatest._
import akka.testkit.{ImplicitSender, TestKit, TestActorRef}
import scala.concurrent.duration._
import scala.concurrent.Await
import akka.pattern.ask
 
import scala.util.Success
 
class HelloActorTests
  extends TestKit(ActorSystem("MySpec"))
  with ImplicitSender
  with WordSpecLike
  with BeforeAndAfterAll
  with Matchers {
 
  override def afterAll {
    TestKit.shutdownActorSystem(system)
  }
 
  "An HelloActor using implicit sender " must {
    "send back 'hello world'" in {
      val helloActor = system.actorOf(Props[HelloActor], name = "helloActor")
      helloActor ! "hello"
      expectMsg("hello world")
    }
  }
}

This is thanks to the fact that we used the ImplicitSender trait, and we used the available akka TestKit assertions.

Expecting A Response

Another thing that is quite common is to expect a response from an actor that we have asked a result from using the Akka ask pattern (which returns a Future[T] to represent the eventual result)

Here is how we might write a test for this:

import akka.actor.{Props, ActorSystem}
import akka.util.Timeout
import org.scalatest._
import akka.testkit.{ImplicitSender, TestKit, TestActorRef}
import scala.concurrent.duration._
import scala.concurrent.Await
import akka.pattern.ask
 
import scala.util.Success
 
class HelloActorTests
  extends TestKit(ActorSystem("MySpec"))
  with ImplicitSender
  with WordSpecLike
  with BeforeAndAfterAll
  with Matchers {
 
  override def afterAll {
    TestKit.shutdownActorSystem(system)
  }
 
  "An HelloActor using TestActorRef " must {
    "send back 'hello world' when asked" in {
      implicit val timeout = Timeout(5 seconds)
      val helloActorRef = TestActorRef(new HelloActor)
      val future = helloActorRef ? "hello"
      val Success(result: String) = future.value.get
      result should be("hello world")
    }
  }
}

It can be seen that we make use of the TestActorRef (which we discussed above the one that uses the CallingThreadDispatcher), as the actor that we use to wrap (for want of a better word) the actual actor we wish to test.

Expecting Exceptions

Another completely plausible thing to want to do is test for exceptions that may be thrown. It can be seen in the HelloActor that we are trying to test that it will throw an IllegalArgumentException should it see a message it doesn’t handle.

So how do we test that?

We use the inbuilt intercept function to allow us to catch the exception.

import akka.actor.{Props, ActorSystem}
import akka.util.Timeout
import org.scalatest._
import akka.testkit.{ImplicitSender, TestKit, TestActorRef}
import scala.concurrent.duration._
import scala.concurrent.Await
import akka.pattern.ask
 
import scala.util.Success 
 
class HelloActorTests
  extends TestKit(ActorSystem("MySpec"))
  with ImplicitSender
  with WordSpecLike
  with BeforeAndAfterAll
  with Matchers {
 
  override def afterAll {
    TestKit.shutdownActorSystem(system)
  }
 
  "An HelloActor using TestActorRef " must {
    "should throw IllegalArgumentException when sent unhandled message" in {
      val actorRef = TestActorRef(new HelloActor)
      intercept[IllegalArgumentException] { actorRef.receive("should blow up") }
    }
  }
}

Testing Finite State Machines

Last time, we looked at implementing finite state machines in Akka. One of the methods we use for that was using Akka FSM. Let's examine how we can test those using the TestKit.

Let's assume we have this simple AkkaFSM example (based on the LightSwitch demo from the last article).

import akka.actor.{Actor, ActorRef, FSM}
import scala.concurrent.duration._
import scala.collection._
 
// received events
final case class PowerOn()
final case class PowerOff()
 
// states
sealed trait LightSwitchState
case object On extends LightSwitchState
case object Off extends LightSwitchState
 
//data
sealed trait LightSwitchData
case object NoData extends LightSwitchData
 
class LightSwitchActor extends FSM[LightSwitchState, LightSwitchData] {
 
  startWith(Off, NoData)
 
  when(Off) {
    case Event(PowerOn, _) =>
      goto(On) using NoData
  }
 
  when(On, stateTimeout = 1 second) {
    case Event(PowerOff, _) =>
      goto(Off) using NoData
    case Event(StateTimeout, _) =>
      println("'On' state timed out, moving to 'Off'")
      goto(Off) using NoData
  }
 
  whenUnhandled {
    case Event(e, s) =>
      log.warning("received unhandled request {} in state {}/{}", e, stateName, s)
      goto(Off) using NoData
  }
 
  onTransition {
    case Off -> On => println("Moved from Off to On")
    case On -> Off => println("Moved from On to Off")
  }
 
  initialize()
}

This example is fairly good as it only has 2 states On/Off. So it makes for quite a good simply example to showcase the testing.

Another Special Test Actor

To test FSMs, there is yet another specialized actor which may only be used for testing FSMs. This actor is call TestFSMRef. Just like the TestActorRef, you use the TestFSMRef to accept the actual FSM actor you are trying to test.

Here is an example of that:

val fsm = TestFSMRef(new LightSwitchActor())

The TestFSMRef comes with a whole host of useful methods, properties that can be used when testing FSMs. We will see some of them used below.

Testing Initial State

As we saw last time, Akka FSM has the idea of an initialise() method that may be used to place the FSM in an initial state. So we should be able to test that.

Here is how we can do that:

import akka.actor.{ActorSystem, Props}
import akka.pattern.ask
import akka.testkit.{ImplicitSender, TestActorRef, TestKit}
import akka.util.Timeout
import org.scalatest._
import akka.testkit.TestFSMRef
import akka.actor.FSM
 
import scala.concurrent.duration._
import scala.util.Success
 
class LightSwitchFSMActorTests
  extends TestKit(ActorSystem("MySpec"))
  with ImplicitSender
  with WordSpecLike
  with BeforeAndAfterAll
  with Matchers {
 
  override def afterAll {
    TestKit.shutdownActorSystem(system)
  }
 
  "An LightSwitchActor " must {
    "start in the 'Off' state" in {
      val fsm = TestFSMRef(new LightSwitchActor())
      assert(fsm.stateName == Off)
      assert(fsm.stateData == NoData)
    }
  }
}
Testing State Move

The TestFSMRef comes with the ability to move the underlying FSM actor into a new state using the setState method. Here is an example of how to use that:

import akka.actor.{ActorSystem, Props}
import akka.pattern.ask
import akka.testkit.{ImplicitSender, TestActorRef, TestKit}
import akka.util.Timeout
import org.scalatest._
import akka.testkit.TestFSMRef
import akka.actor.FSM
 
import scala.concurrent.duration._
import scala.util.Success 
 
class LightSwitchFSMActorTests
  extends TestKit(ActorSystem("MySpec"))
  with ImplicitSender
  with WordSpecLike
  with BeforeAndAfterAll
  with Matchers {
 
  override def afterAll {
    TestKit.shutdownActorSystem(system)
  }
 
  "An LightSwitchActor that starts with 'Off' " must {
    "should transition to 'On' when told to by the test" in {
      val fsm = TestFSMRef(new LightSwitchActor())
      fsm.setState(stateName = On)
      assert(fsm.stateName == On)
      assert(fsm.stateData == NoData)
    }
  }
}

You can of course still send messages to the TestFSMRef which would instruct the underlying FSM actor to move to a new state.

import akka.actor.{ActorSystem, Props}
import akka.pattern.ask
import akka.testkit.{ImplicitSender, TestActorRef, TestKit}
import akka.util.Timeout
import org.scalatest._
import akka.testkit.TestFSMRef
import akka.actor.FSM
 
import scala.concurrent.duration._
import scala.util.Success 
 
class LightSwitchFSMActorTests
  extends TestKit(ActorSystem("MySpec"))
  with ImplicitSender
  with WordSpecLike
  with BeforeAndAfterAll
  with Matchers {
 
  override def afterAll {
    TestKit.shutdownActorSystem(system)
  }
 
  "An LightSwitchActor that starts with 'Off' " must {
    "should transition to 'On' when sent a 'PowerOn' message" in {
      val fsm = TestFSMRef(new LightSwitchActor())
      fsm ! PowerOn
      assert(fsm.stateName == On)
      assert(fsm.stateData == NoData)
    }
  }
}
Testing StateTimeout

Another thing that AkkaFSM supports is the notion of a StateTimeout. In the example FSM we are trying to test, if the FSM stays in the On state for more than 1 second, it should automatically move to the Off state.

So how do we test that?

Here is how:

import akka.actor.{ActorSystem, Props}
import akka.pattern.ask
import akka.testkit.{ImplicitSender, TestActorRef, TestKit}
import akka.util.Timeout
import org.scalatest._
import akka.testkit.TestFSMRef
import akka.actor.FSM
 
import scala.concurrent.duration._
import scala.util.Success 
 
class LightSwitchFSMActorTests
  extends TestKit(ActorSystem("MySpec"))
  with ImplicitSender
  with WordSpecLike
  with BeforeAndAfterAll
  with Matchers {
 
  override def afterAll {
    TestKit.shutdownActorSystem(system)
  }
 
  "An LightSwitchActor that stays 'On' for more than 1 second " must {
    "should transition to 'Off' thanks to the StateTimeout" in {
      val fsm = TestFSMRef(new LightSwitchActor())
      fsm ! PowerOn
      awaitCond(fsm.stateName == Off, 1200 milliseconds, 100 milliseconds)
    }
  }
}

Testing Using Probes

So far, we have been looking at testing a single actor that might reply to a single sender. Sometimes though we may need to test an enture suite of actors all working together. And due to the single threaded nature of the TestActorRef (thanks to the very useful CurrentThreadDispatcher), we may find it difficult to distinguish the incoming messages read.

Akka TestKit provides yet another abstraction to deal with this, which is the idea of a concrete actor that you inject into the message flow. This concept is called a TestProbe.

Let's assume we have this actor that replies to 2 actorRef.

import akka.actor.{ActorRef, Actor}
 
class DoubleSenderActor extends Actor {
  var dest1: ActorRef = _
  var dest2: ActorRef = _
  def receive = {
    case (d1: ActorRef, d2: ActorRef) =>
      dest1 = d1
      dest2 = d2
    case x =>
      dest1 ! x
      dest2 ! x
  }
}

We could test this code as follows using the TestProbe.

import akka.actor.{ActorSystem, Props}
import akka.pattern.ask
import akka.testkit.{TestProbe, ImplicitSender, TestActorRef, TestKit}
import akka.util.Timeout
import org.scalatest._
 
import scala.concurrent.duration._
import scala.util.Success
 
class DoubleSenderActorTestsUsingProbe
  extends TestKit(ActorSystem("MySpec"))
  with ImplicitSender
  with WordSpecLike
  with BeforeAndAfterAll
  with Matchers {
 
  override def afterAll {
    TestKit.shutdownActorSystem(system)
  }
 
  "An DoubleSenderActor that has 2 target ActorRef for sending messages to " must {
    "should send messages to both supplied 'TestProbe(s)'" in {
      val doubleSenderActor = system.actorOf(Props[DoubleSenderActor],
        name = "multiSenderActor")
      val probe1 = TestProbe()
      val probe2 = TestProbe()
      doubleSenderActor ! ((probe1.ref, probe2.ref))
      doubleSenderActor ! "hello"
      probe1.expectMsg(500 millis, "hello")
      probe2.expectMsg(500 millis, "hello")
    }
  }
}

It can be seen that the TestProbe comes with its own set of useful assertion methods. This is due to the fact that TestProbe inherits from the TestKit trait, and as such you can expect to find ALL the TestKit traits assertions available to use when using TestProbe objects.

Where Can I Find The Code Examples?

I will be augmenting this GitHub repo with the example projects as I move through this series.

This article was originally posted at https://sachabarbs.wordpress.com/2016/09/14/akka-testkit

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
-- There are no messages in this forum --