Scala

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.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button