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.
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.
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.
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.
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.
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.
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.
moduleName = akka-spock-module
moduleVersion = 1.0
extensionClasses = ua.eshepelyuk.blog.ActorSystemExtensionModule
Then we are ready to implement extension module functionality.
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.
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 |