Testing AKKA application with Spock
AKKA
is message-driven and actor model based concurrency toolkit. Although it’s written in Scala
, AKKA
can be used in any JVM
based language project. This post tries to fill the gap of missing information about writing good tests in polyglot JVM projects that leverage AKKA
framework. In multi language JVM projects my obvious choice of testing tool is Spock
. Powered by Groovy
and JUnit
, this tool makes writing tests a much more fun.
This article is not intended to be AKKA
or Spock
tutorial. The audience is assumed to be knowing Groovy
and Spock
basics, as well as basics of actor model concurrency.
Using AKKA TestKit framework for testing actors
For our purposes let’s create a simple actor that receives message, prefixes it with Hello
and sends the result back to the original sender.
HelloActor.java
public class HelloActor extends UntypedActor { @Override public void onReceive(Object message) throws Exception { sender().tell("Hello " + Objects.toString(message.toString()), self()); } }
Testing AKKA actors is quite is straightforward even from non Scala
project. Thanks to great TestKit
framework described in Testing Actor Systems. Simple test can be written as shown below.
HelloActorTest.groovy
class HelloActorTest extends Specification { @AutoCleanup("shutdown") (1) def actorSystem = ActorSystem.create() def probe = new JavaTestKit(actorSystem) (2) def "actor should say hello"() { given: def helloActor = actorSystem.actorOf(Props.create(HelloActor)) when: helloActor.tell("world", probe.ref) (3) then: probe.expectMsgEquals("Hello world") (4) } }
(1) annotation telling Spock
to cleanup variable after test ends, calling mentioned method, i.e. shutdown
(2) JavaTestKit
is the core for TestKit framework, providing tools for interacting with actors
(3) send a world
string as a message for the actor, passing JavaTestKit
instance as a message sender
(4) asserting that probe
received back proper message, i.e. prefixed with Hello
Testing AKKA extensions
AKKA extensions is lightweight and powerful way of extending core AKKA functionality with project specific features. Let’s enhance our system with possibility of using arbitrary greeting, instead of hard-coded Hello
. For this purpose – we can create AKKA extension, named GreetExtension
, with single method exposed. Calling the method will return random greeting word from predefined list.
GreetExtension.java
public class GreetExtension implements Extension { public static final ExtensionKey<GreetExtension> KEY = new ExtensionKey<GreetExtension>(GreetExtension.class) {}; (1) private final Random random; private final ExtendedActorSystem actorSystem; public GreetExtension(ExtendedActorSystem actorSystem) { this.actorSystem = actorSystem; this.random = new Random(); } public static final List<String> GREET_WORDS = Arrays.asList("Hello", "Nice to meet you", "What's up"); public String greetWord() { return GREET_WORDS.get(random.nextInt(GREET_WORDS.size())); (2) } }
(1) unique identifier, allowing to obtain extension from ActorSystem
instance
(2) randomly pick up any of available greeting word
For the illustration of AKKA extension usage let’s create modified version of HelloActor
– named GreetExtensionActor
. Its behavior will differ from original by usage of GreetExtension
to generate a response. Actor will ask extension for the greeting word, prefix original message with it and then reply to the message’s sender.
GreetExtensionActor.groovy
public class GreetExtensionActor extends UntypedActor { @Override public void onReceive(Object message) throws Exception { GreetExtension greetExtension = GreetExtension.KEY.get(context().system()); (1) sender().tell(greetExtension.greetWord() + " " + Objects.toString(message), self()); } }
(1) obtain AKKA extension by its identifier
Using AKKA TestKit for testing AKKA extension aware actors
We could modify HelloActorTest.java
test suite for GreetExtensionActor
in such way.
GreetExtensionActorTest.groovy
def "actor should greet via AKKA extension"() { given: def helloActor = actorSystem.actorOf(Props.create(GreetExtensionActor)) when: helloActor.tell("world", probe.ref) then: def msg = probe.expectMsgClass(String) msg.endsWith("world") && GreetExtension.GREET_WORDS.any { msg.startsWith(it) } (1) }
(1) since prefix is randomly generated – we can’t check exact match, instead we’re checking that response message is prefixed with one of possible values
Mocking AKKA extension
The obvious drawback of test case above is dependency on GreetExtension
whose behavior is non-deterministic. GreetExtensionActor
can’t be tested in isolation and can’t be tested with single defined set of input / output values. To overcome this – most apparent option is to use mocking and inject mock of GreetExtension
into actor system. Mocking and stubbing functionality is provided by Spock
itself, but unluckily AKKA
doesn’t provide API to replace AKKA extension with stub instance. Fortunately, due to Groovy
nature it’s possible to access private members of ActorSystem
. Using this trick we could manually replace AKKA extension instance with our stub and become able to write a test case with defined input / output.
GreetExtensionActorTest.groovy
def "actor should greet via mocked AKKA extension"() { given: def helloActor = actorSystem.actorOf(Props.create(GreetExtensionActor)) and: GreetExtension.KEY.get(actorSystem) actorSystem.extensions[GreetExtension.KEY] = Stub(GreetExtension) { (1) greetWord() >> "Bye" } when: helloActor.tell("world", probe.ref) then: probe.expectMsgClass(String) == "Bye world" }
(1) magic here, accessing internals of actor system, adjusting its value with extension stub
Extending Actor System functionality using Groovy extension modules
Looking at previous test, the piece of code can be detected, that could be subject of duplication across tests cases. The code is used for replacing actual AKKA extension with mock.
GreetExtension.KEY.get(actorSystem) actorSystem.extensions[GreetExtension.KEY] = Stub(GreetExtension) { greetWord() >> "Bye" }
It would be great if we can extract this into utility method and then use it where needed. One of possibility is to use Groovy
traits and mix the trait into each Spock
specification class. Another option that seems less verbose is to be able to enhance ActorSystem
with new method that will do the job. Luckily, Groovy
has a way to do it using Extension Modules.
We could in runtime add method to any class that will be visible only for tests classes, without affecting production code. To enable it we have to put file named org.codehaus.groovy.runtime.ExtensionModule
into test/resources/META-INF/services
folder.
org.codehaus.groovy.runtime.ExtensionModule
moduleName = akka-spock-module moduleVersion = 1.0 extensionClasses = ua.eshepelyuk.blog.ActorSystemExtensionModule
Then we are ready to implement extension module functionality.
ActorSystemExtensionModule.groovy
class ActorSystemExtensionModule { static <T extends Extension> void mockAkkaExtension(ActorSystem actorSystem, ExtensionId<T> extId, T mock) { extId.get(actorSystem) actorSystem.extensions[extId] = mock } }
So, having ActorSystem
enhanced with mockAkkaExtension
method we could finally rewrite test case as below.
GreetExtensionActorTest.groovy
def "actor should greet with mocked AKKA extension, using Groovy extension module"() { given: def helloActor = actorSystem.actorOf(Props.create(GreetExtensionActor)) and: actorSystem.mockAkkaExtension(GreetExtension.KEY, Stub(GreetExtension) { (1) greetWord() >> "Bye cruel" }) when: helloActor.tell("world", probe.ref) then: probe.expectMsgClass(String) == "Bye cruel world" }
(1) calling method on ActorSystem
instance, that doesn’t exist in Scala
code, it’s added by our ActorSystemExtensionModule
- Full project’s code is available at My GitHub
Reference: | Testing AKKA application with Spock from our JCG partner Evgeny Shepelyuk at the jk’s blog blog. |