Java Based Akka application Part 2: Adding tests

On the previous blog we focused on spinning up our first Akka project.
Now it’s time to add a test for our codebase.

First thing to get started is adding the right dependencies to the existing project.

	<dependencies>
		<dependency>
			<groupId>com.typesafe.akka</groupId>
			<artifactId>akka-actor-typed_2.13</artifactId>
			<version>${akka.version}</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.16</version>
			<scope>provided</scope>
		</dependency>

		<!-- Test -->
		<dependency>
			<groupId>com.typesafe.akka</groupId>
			<artifactId>akka-actor-testkit-typed_2.13</artifactId>
			<version>${akka.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.13.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

What you shall notice is the usage of Junit 4, instead of Junit 5. Some of the testing utils like TestKitJunitResource need annotations like @ClassRule and are bound to Junit4. Obviously this is not a blocker on using JUnit 5, with some tweaks it is feasible to use the tools your project needs. However in this example Junit 4 shall be used.

Before we write the test we need to think of our code.
It is obvious that we sent a message to our actor in a fire and forget fashion.

	private Behavior<GuardianMessage> receiveMessage(MessageToGuardian messageToGuardian) {
		getContext().getLog().info("Message received: {}",messageToGuardian.getMessage());
		return this;
	}

If you don’t have a way to intercept what happens inside the method your options are limited. In those cases you can utilise the log messages and actually expect log events to happen.

Before we add the unit test we need to make some logback adjustments. This will take effect only on our test logback.xml. More specific we need to have an appender on logback that captured the data. This is the CapturingAppender.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <pattern>[%date{ISO8601}] [%level] [%logger] [%marker] [%thread] - %msg MDC: {%mdc}%n</pattern>
        </encoder>
    </appender>

    <!-- Logging from tests are silenced by this appender. When there is a test failure the captured logging events are flushed to the appenders defined for the akka.actor.testkit.typed.internal.CapturingAppenderDelegate logger. -->
    <appender name="CapturingAppender" class="akka.actor.testkit.typed.internal.CapturingAppender" />

    <!-- The appenders defined for this CapturingAppenderDelegate logger are used when there is a test failure and all logging events from the test are flushed to these appenders. -->
    <logger name="akka.actor.testkit.typed.internal.CapturingAppenderDelegate" >
      <appender-ref ref="STDOUT"/>
    </logger>

    <root level="DEBUG">
        <appender-ref ref="CapturingAppender"/>
    </root>
</configuration>

Now it’s time to add the unit test.

package com.gkatzioura;

import akka.actor.testkit.typed.javadsl.LogCapturing;
import akka.actor.testkit.typed.javadsl.LoggingTestKit;
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
import akka.actor.testkit.typed.javadsl.TestProbe;
import akka.actor.typed.ActorRef;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;

public class AppGuardianTests {

	@ClassRule
	public static final TestKitJunitResource testKit = new TestKitJunitResource();

	@Rule
	public final LogCapturing logCapturing = new LogCapturing();

	@Test
	public void testReceiveMessage() {
		ActorRef<AppGuardian.GuardianMessage> underTest = testKit.spawn(AppGuardian.create(), "app-guardian");

		LoggingTestKit.info("Message received: hello")
				.expect(
						testKit.system(),
						() -> {
							underTest.tell(new AppGuardian.MessageToGuardian("hello"));
							return null;
						});
	}

}

Once we run the test the expected outcome is to pass. The actor did receive the message, did execute a logging action and this was captured by the CapturingAppender. Then the logging event was validated if it was the expected one. In case of exception probably you need to check if the logback.xml took effect.

As always you can find the source code on github.

Advertisement

Java Based Akka application Part 1: Your base Project

Akka is a free, open-source toolkit and runtime for building highly concurrent, distributed, and resilient message-driven applications on the JVM. Along with Akka you have akka-streams  a module that makes the ingestion and processing of streams easy  and Alpakka, a Reactive Enterprise Integration library for Java and Scala, based on Reactive Streams and Akka.

On this blog I shall focus on creating an Akka project using Java as well as packaging it.

 

You already know that Akka is built on Scala, thus why Java and no Scala? There are various reasons to go for Java.

  • Akka is a toolkit running on the JVM so you don’t have to be proficient with Scala to use it.
  • You might have a team already proficient with Java but not in Scala.
  • It’s much easier to evaluate if you already have a codebase on Java and the various build tools (maven etc)

Will shall go for the simple route and Download the Application from lightbend quickstart. The project received, will be backed with typed actors.

After some adaption the maven file would look like this, take note that we shall use lombok .

<project>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gkatzioura</groupId>
    <artifactId>akka-java-app</artifactId>
    <version>1.0</version>

    <properties>
      <akka.version>2.6.10</akka.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor-typed_2.13</artifactId>
            <version>${akka.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <configuration>
                    <executable>java</executable>
                    <arguments>
                        <argument>-classpath</argument>
                        <classpath />
                        <argument>com.gkatzioura.Application</argument>
                    </arguments>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Now there is one Actor that is responsible for managing your other actors. This is the top level actor called Guardian Actor. It is created along with the ActorSystem and when it stops the ActorSystem will stop too.

In order to create an actor you define the message the actor will receive and you specify why it will behave to those messages.

package com.gkatzioura;

import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.AbstractBehavior;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import akka.actor.typed.javadsl.Receive;
import lombok.AllArgsConstructor;
import lombok.Getter;

public class AppGuardian extends AbstractBehavior<AppGuardian.GuardianMessage> {

	public interface GuardianMessage {}

	static Behavior<GuardianMessage> create() {
		return Behaviors.setup(AppGuardian::new);
	}

	@Getter
	@AllArgsConstructor
	public static class MessageToGuardian implements GuardianMessage {
		private String message;
	}

	private AppGuardian(ActorContext<GuardianMessage> context) {
		super(context);
	}

	@Override
	public Receive<GuardianMessage> createReceive() {
		return newReceiveBuilder().onMessage(MessageToGuardian.class, this::receiveMessage).build();
	}

	private Behavior<GuardianMessage> receiveMessage(MessageToGuardian messageToGuardian) {
		getContext().getLog().info("Message received: {}",messageToGuardian.getMessage());
		return this;
	}

}

Akka is message driven so the guardian actor should be able to consume messages send to it. Therefore messages that implement the GuardianMessage interface are going to be processed.

By creating the actor the createReceive method is used in order to add handling of the messages that the actor should handle.

Be aware that when it comes to logging instead of spinning up a logger in the class use the
getContext().getLog()

Behind the scenes the log messages will have the path of the actor automatically added as akkaSource Mapped Diagnostic Context (MDC) value.

Last step would be to add the Main class.

package com.gkatzioura;

import java.io.IOException;

import akka.actor.typed.ActorSystem;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Application {

	public static final String APP_NAME = "akka-java-app";

	public static void main(String[] args) {
		final ActorSystem<AppGuardian.GuardianMessage> appGuardian = ActorSystem.create(AppGuardian.create(), APP_NAME);
		appGuardian.tell(new AppGuardian.MessageToGuardian("First Akka Java App"));

		try {
			System.out.println(">>> Press ENTER to exit <<<");
			System.in.read();
		}
		catch (IOException ignored) {
		}
		finally {
			appGuardian.terminate();
		}
	}

}

The expected outcome is to have our Guardian actor to print the message submitted. By pressing enter the Akka application will terminate through the guardian actor.
On the next blog we will go one step further and add a unit test that validates the message received.
As always you can find the source code on github.