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.