NOTE: These patterns assume you have a working understanding of Akka's JavaTestKit
AKKA is all about building hierarchies of actors to represent a system. I've found this is a great way to decompose, design, and if needed distribute a system. This means your system becomes a hierarchy of actors, where parent actors create and manage their child actors, and child actors either carry out some unit of work, and/or themselves become parent actors.
But this can be a bit of a pain when it comes to writing tests for your actors. For example, say we have an actor, actorA, who in turn creates one or more child actors. I could have actorA create its child actor(s) as part of its instantiation. This is attractive especially if I know the exact child actors its going to create.
But how do I go about testing actorA in complete isolation from it's parent or child actors? My test needs to create an instance of the actorA, and not have to create it's parent actors, or any of its child actors. If I create an ActorRef of actorA, its going to automatically create all its child actors along with it. Not what I want. Not only that, but as my unit test sends messages to actorA, its probably going to send messages to its children, who will in turn do some bit of work as a response to that message. Definitely not what I want (at least in an isolated test).
There are two standard ways around this. First, is to pass in Props instances for the desired child actors into the parent actor's constructor. The second way is to pass in a known object as a message to the parent actor, and the parent actor uses this message to create a child actor. I use a simple ChildArgs class for this that has two fields, a Props for actor creation, and a String for the actor's name. Either method will make unit testing easier, since if you want to spin up actor1 for an isolated test, just don't pass in any Props and no child actors will get created. I tend to use the constructor method, because it makes it easier to define your Actor hierarchy in Spring.
Testing an actor in isolation is a good goal, but often an actor will send a message to its child as a reaction to an incoming message. So how do we test this behavior if we don't want the actor under test to spin up its children actors? Use a ForwarderActor.
The nice thing about having parent actors create their children via passed in Props is that the parent can now create ANY actor as their child, not just the expected one. All you have to do is pass in a Props instance, and the actor will create a child.
Now, in order to capture and test messages that an actor should send to its child, you configure a Props that creates a ForwarderActor, and pass in a JavaTestKit into the ForwarderActor's constructor. Then you pass in this Props to your actor under test's constructor. The actor under test, gets the Props instance, creates its child actor from the Props, and any message the actor under test should send to its child will get captured by the JavaTestKit instance.
For example:
new JavaTestKit(system) {{
//create a probe instance to capture all messages passed to child
final JavaTestKit childProbe = new JavaTestKit(system);
//create Props to create fake child actor
final Props childProps = Props.create(ForwarderActor.class, childProbe.getRef());
//create Props for the actor being tested
final Props workerProps = Props.create(SomeWorkerActor.class, childProps);
//create the actor being tested and test it
ActorRef actor = system.actorOf(props, "someName");
//send actor being tested a message
actor.tell("do something", this.getRef());
//test to see if child probe received a message in response to its parent getting "do something"
childProbe.expectMsgEquals(FiniteDuration.create(0, TimeUnit.SECONDS), "something happened");
}};
This allows us to assert that when we send a message to the actor being tested, it in turn sends a specific message to its child actor. We don't care that the child actor wasn't the one the parent was expecting. This way we can write tests that only exercise a single actor, yet we can also test that the messages it was supposed to send out to other actors were actually sent.
Here is another actor scenario thats hard to test in full isolation. What if the code being tested needs to reference an actor via its full akka path, but you don't want to create all the actor's parents up the hierarchy?
For example, lets say we have a worker actor, who's parent is a supervisor actor, who's parent is a top level system manager actor (this is a totally made up scenario just to demonstrate the problem). The code under test references the worker actor via its path, "/user/system/supervisor/worker". Now, in order to call system.actorSelection("/user/system/supervisor/worker")
the test needs to make sure there is a "system" actor, who has a child "supervisor" actor, who has a child "worker" actor.
To do this I use a ChildCreationActor. This is a very straight forward actor that only expects one type of message. A ChildArgs instance, which has a Props field for actor creation and a String field for the actor name. Using this actor we can create the necessary hierarchy of parent actors required to run the test.
new JavaTestKit(system) {{
//create top level system actor
ActorRef sysActor = system.actorOf(Props.create(ChildCreationActor.class), "system");
//tell system actor to create second level supervisor actor
sysActor.tell(
new ChildCreationActor.ChildArgs(Props.create(ChildCreationActor.class), "supervisor"),
this.getRef());
//tell supervisor actor to create third level worker actor
system.actorSelection("user/system/supervisor").tell(
new ChildCreationActor.ChildArgs(Props.create(SomeWorkerActor.class), "worker"),
this.getRef());
//now we can test the worker actor via its akka path
system.actorSelection("user/system/supervisor/worker").tell("stuff", this.getRef());
}};
We now have a way to write tests that interact with a worker actor via its AKKA path, in complete isolation from the worker's parent actors.
One last pattern that I run into a lot when testing Akka systems, is writing test for components that send messages to an actor and expect some return message at some point, especially if the function under test uses Patterns.ask(). In order to test this function in isolation, we don't want the actually intended actor receiving the message because this could have unintended downstream affects. But we do require the actor to send a reply message so our unit test will pass.
To handle this situation I use a ParrotActor, which is basically a mock actor. Its an actor that you pre-configure with expected input messages, and the appropriate response message. Combine this with the ChildCreationActor and your test can create an actor with the appropriate path name, which will send the correct response to the input message your test invokes.
//test setup...create the ParrotActor with the actor name/path the test expects
final Props someActorProps = Props.create(ParrotActor.class);
final ActorRef someActor = system.actorOf(someActorProps, "someActor");
//register a response message with the parrot actor
someActor.tell(
new ParrotActor.Squawk(String.class, "response message"),
ActorRef.noSender());
//test code
ActorSelection actor = system.actorSelection("/user/someActor");
final Future<Object> futureRsp = Patterns.ask(actor, "input message", Timeout.apply(3, TimeUnit.SECONDS));
//use the future to get the response message
Currently the ParrotActor uses the class type of the input message to figure out what response message to send. I always send custom classes as actor messages instead of strings, but it would be easy to extend the ParrotActor to register individual string instances as messages and responses if that is what is required.