Testing

Dependency

To use Akka TestKit Typed, add the module to your project:

sbt
libraryDependencies += "com.typesafe.akka" %% "akka-actor-testkit-typed" % "2.5.18" % Test
Maven
<dependency>
  <groupId>com.typesafe.akka</groupId>
  <artifactId>akka-actor-testkit-typed_2.12</artifactId>
  <version>2.5.18</version>
  <scope>test</scope>
</dependency>
Gradle
dependencies {
  test group: 'com.typesafe.akka', name: 'akka-actor-testkit-typed_2.12', version: '2.5.18'
}

Introduction

Testing can either be done asynchronously using a real ActorSystem or synchronously on the testing thread using the BehaviousTestKit.

For testing logic in a Behavior in isolation synchronous testing is preferred. For testing interactions between multiple actors a more realistic asynchronous test is preferred.

Certain Behaviors will be hard to test synchronously e.g. if they spawn Future’s and you rely on a callback to complete before observing the effect you want to test. Further support for controlling the scheduler and execution context used will be added.

Warning

This module is currently marked as may change in the sense of being the subject of active research. This means that API or semantics can change without warning or deprecation period and it is not recommended to use this module in production just yet—you have been warned.

Synchronous behavior testing

The following demonstrates how to test:

  • Spawning child actors
  • Spawning child actors anonymously
  • Sending a message either as a reply or to another actor
  • Sending a message to a child actor

The examples below require the following imports:

Scala
import akka.actor.testkit.typed.Effect._
import akka.actor.testkit.typed.scaladsl.BehaviorTestKit
import akka.actor.testkit.typed.scaladsl.TestInbox
import akka.actor.typed._
import akka.actor.typed.scaladsl._
Java
import akka.actor.testkit.typed.javadsl.BehaviorTestKit;
import akka.actor.testkit.typed.javadsl.Effects;
import akka.actor.testkit.typed.javadsl.TestInbox;
import akka.actor.typed.*;
import akka.actor.typed.javadsl.*;

Each of the tests are testing an actor that based on the message executes a different effect to be tested:

Scala
sealed trait Cmd
case object CreateAnonymousChild extends Cmd
case class CreateChild(childName: String) extends Cmd
case class SayHelloToChild(childName: String) extends Cmd
case object SayHelloToAnonymousChild extends Cmd
case class SayHello(who: ActorRef[String]) extends Cmd

val myBehavior = Behaviors.receivePartial[Cmd] {
  case (context, CreateChild(name)) ⇒
    context.spawn(childActor, name)
    Behaviors.same
  case (context, CreateAnonymousChild) ⇒
    context.spawnAnonymous(childActor)
    Behaviors.same
  case (context, SayHelloToChild(childName)) ⇒
    val child: ActorRef[String] = context.spawn(childActor, childName)
    child ! "hello"
    Behaviors.same
  case (context, SayHelloToAnonymousChild) ⇒
    val child: ActorRef[String] = context.spawnAnonymous(childActor)
    child ! "hello stranger"
    Behaviors.same
  case (_, SayHello(who)) ⇒
    who ! "hello"
    Behaviors.same
}
Java
interface Command { }
public static class CreateAChild implements Command {
  private final String childName;
  public CreateAChild(String childName) {
    this.childName = childName;
  }
}
public static class CreateAnAnonymousChild implements Command { }
public static class SayHelloToChild implements Command {
  private final String childName;
  public SayHelloToChild(String childName) {
    this.childName = childName;
  }
}
public static class SayHelloToAnonymousChild implements Command { }
public static class SayHello implements Command {
  private final ActorRef<String> who;

  public SayHello(ActorRef<String> who) {
    this.who = who;
  }
}

public static Behavior<Command> myBehavior = Behaviors.receive(Command.class)
  .onMessage(CreateAChild.class, (context, message) -> {
    context.spawn(childActor, message.childName);
    return Behaviors.same();
  })
  .onMessage(CreateAnAnonymousChild.class, (context, message) -> {
    context.spawnAnonymous(childActor);
    return Behaviors.same();
  })
  .onMessage(SayHelloToChild.class, (context, message) -> {
    ActorRef<String> child = context.spawn(childActor, message.childName);
    child.tell("hello");
    return Behaviors.same();
  })
  .onMessage(SayHelloToAnonymousChild.class, (context, message) -> {
    ActorRef<String> child = context.spawnAnonymous(childActor);
    child.tell("hello stranger");
    return Behaviors.same();
  }).onMessage(SayHello.class, (context, message) -> {
    message.who.tell("hello");
    return Behaviors.same();
  }).build();

For creating a child actor a noop actor is created:

Scala
val childActor = Behaviors.receiveMessage[String] { _ ⇒
  Behaviors.same[String]
}
Java
public static Behavior<String> childActor = Behaviors.receive((context, message) -> Behaviors.same());

All of the tests make use of the BehaviorTestkit to avoid the need for a real ActorContext. Some of the tests make use of the TestInbox which allows the creation of an ActorRef that can be used for synchronous testing, similar to the TestProbe used for asynchronous testing.

Spawning children

With a name:

Scala
val testKit = BehaviorTestKit(myBehavior)
testKit.run(CreateChild("child"))
testKit.expectEffect(Spawned(childActor, "child"))
Java
BehaviorTestKit<Command> test = BehaviorTestKit.create(myBehavior);
test.run(new CreateAChild("child"));
test.expectEffect(Effects.spawned(childActor, "child", Props.empty()));

Anonymously:

Scala
val testKit = BehaviorTestKit(myBehavior)
testKit.run(CreateAnonymousChild)
testKit.expectEffect(SpawnedAnonymous(childActor))
Java
BehaviorTestKit<Command> test = BehaviorTestKit.create(myBehavior);
test.run(new CreateAnAnonymousChild());
test.expectEffect(Effects.spawnedAnonymous(childActor, Props.empty()));

Sending messages

For testing sending a message a TestInbox is created that provides an ActorRef and methods to assert against the messages that have been sent to it.

Scala
val testKit = BehaviorTestKit(myBehavior)
val inbox = TestInbox[String]()
testKit.run(SayHello(inbox.ref))
inbox.expectMessage("hello")
Java
BehaviorTestKit<Command> test = BehaviorTestKit.create(myBehavior);
TestInbox<String> inbox = TestInbox.create();
test.run(new SayHello(inbox.getRef()));
inbox.expectMessage("hello");

Another use case is sending a message to a child actor you can do this by looking up the ‘TestInbox’ for a child actor from the ‘BehaviorTestKit’:

Scala
val testKit = BehaviorTestKit(myBehavior)
testKit.run(SayHelloToChild("child"))
val childInbox = testKit.childInbox[String]("child")
childInbox.expectMessage("hello")
Java
BehaviorTestKit<Command> testKit = BehaviorTestKit.create(myBehavior);
testKit.run(new SayHelloToChild("child"));
TestInbox<String> childInbox = testKit.childInbox("child");
childInbox.expectMessage("hello");

For anonymous children the actor names are generated in a deterministic way:

Scala
val testKit = BehaviorTestKit(myBehavior)
testKit.run(SayHelloToAnonymousChild)
val child = testKit.expectEffectType[SpawnedAnonymous[String]]

val childInbox = testKit.childInbox(child.ref)
childInbox.expectMessage("hello stranger")
Java
BehaviorTestKit<Command> testKit = BehaviorTestKit.create(myBehavior);
testKit.run(new SayHelloToAnonymousChild());
// Anonymous actors are created as: $a $b etc
TestInbox<String> childInbox = testKit.childInbox("$a");
childInbox.expectMessage("hello stranger");

Testing other effects

The BehaviorTestkit keeps track other effects you can verify, look at the sub-classes of akka.actor.testkit.typed.Effect

  • SpawnedAdapter
  • Stopped
  • Watched
  • Unwatched
  • Scheduled

See the other public methods and API documentation on BehaviorTestkit for other types of verification.

Asynchronous testing

Asynchronous testing uses a real ActorSystem that allows you to test your Actors in a more realistic environment.

The minimal setup consists of the test procedure, which provides the desired stimuli, the actor under test, and an actor receiving replies. Bigger systems replace the actor under test with a network of actors, apply stimuli at varying injection points and arrange results to be sent from different emission points, but the basic principle stays the same in that a single procedure drives the test.

Basic example

Actor under test:

Scala
case class Ping(message: String, response: ActorRef[Pong])
case class Pong(message: String)

val echoActor: Behavior[Ping] = Behaviors.receive { (_, message) ⇒
  message match {
    case Ping(m, replyTo) ⇒
      replyTo ! Pong(m)
      Behaviors.same
  }
}
Java
public static class Ping {
  private String message;
  private ActorRef<Pong> replyTo;

  public Ping(String message, ActorRef<Pong> replyTo) {
    this.message = message;
    this.replyTo = replyTo;
  }
}
public static class Pong {
  private String message;

  public Pong(String message) {
    this.message = message;
  }
}

Behavior<Ping> echoActor = Behaviors.receive((context, ping) -> {
  ping.replyTo.tell(new Pong(ping.message));
  return Behaviors.same();
});

Tests create an instance of ActorTestKit. This provides access to:

  • An ActorSystem
  • Methods for spawning Actors. These are created under the root guardian
  • A method to shut down the ActorSystem from the test suite
Scala
class AsyncTestingExampleSpec extends WordSpec with BeforeAndAfterAll {
  val testKit = ActorTestKit()
Java
public class AsyncTestingExampleTest {
  final static ActorTestKit testKit = ActorTestKit.create();

Your test is responsible for shutting down the ActorSystem e.g. using BeforeAndAfterAll when using ScalaTest@AfterClass when using JUnit.

Scala
override def afterAll(): Unit = testKit.shutdownTestKit()
Java
@AfterClass
public void cleanup() {
  testKit.shutdownTestKit();
}

The following demonstrates:

  • Creating a typed actor from the TestKit’s system using spawn
  • Creating a typed TestProbe
  • Verifying that the actor under test responds via the TestProbe
Scala
val pinger = testKit.spawn(echoActor, "ping")
val probe = testKit.createTestProbe[Pong]()
pinger ! Ping("hello", probe.ref)
probe.expectMessage(Pong("hello"))
Java
ActorRef<Ping> pinger = testKit.spawn(echoActor, "ping");
TestProbe<Pong> probe = testKit.createTestProbe();
pinger.tell(new Ping("hello", probe.ref()));
probe.expectMessage(new Pong("hello"));

Actors can also be spawned anonymously:

Scala
val pinger = testKit.spawn(echoActor)
Java
ActorRef<Ping> pinger = testKit.spawn(echoActor);

Note that you can add import testKit._ to get access to the spawn and createTestProbe methods at the top level without prefixing them with testKit.

Test framework integration

If you are using JUnit you can use akka.actor.testkit.typed.javadsl.TestKitJunitResource to have the async test kit automatically shutdown when the test is complete.

Note that the dependency on JUnit is marked as optional from the test kit module, so your project must explicitly include a dependency on JUnit to use this.

If you are using ScalaTest you can extend akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit to have the async test kit automatically shutdown when the test is complete. This is done in afterAll from the BeforeAndAfterAll trait. If you override that method you should call super.afterAll to shutdown the test kit.

Note that the dependency on ScalaTest is marked as optional from the test kit module, so your project must explicitly include a dependency on ScalaTest to use this.

Scala
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
import org.scalatest.WordSpecLike

class ScalaTestIntegrationExampleSpec extends ScalaTestWithActorTestKit with WordSpecLike {

  "Something" must {
    "behave correctly" in {
      val probe = createTestProbe[String]()
      // ... assertions etc.
    }
  }
}
Java
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
import akka.actor.testkit.typed.javadsl.TestProbe;
import org.junit.ClassRule;
import org.junit.Test;

public class JunitIntegrationExampleTest {

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

  @Test
  public void testSomething() {
    TestProbe<String> probe = testKit.createTestProbe();
    // ... assertions etc.
  }

}

Controlling the scheduler

It can be hard to reliably unit test specific scenario’s when your actor relies on timing: especially when running many tests in parallel it can be hard to get the timing just right. Making such tests more reliable by using generous timeouts make the tests take a long time to run.

For such situations, we provide a scheduler where you can manually, explicitly advance the clock.

Scala
import scala.concurrent.duration._
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
import akka.actor.testkit.typed.scaladsl.ManualTime
import akka.actor.testkit.typed.scaladsl.TestProbe
import akka.actor.typed.scaladsl.Behaviors
import org.scalatest.WordSpecLike

class ManualTimerExampleSpec extends ScalaTestWithActorTestKit(ManualTime.config) with WordSpecLike {

  val manualTime: ManualTime = ManualTime()

  "A timer" must {
    "schedule non-repeated ticks" in {
      case object Tick
      case object Tock

      val probe = TestProbe[Tock.type]()
      val behavior = Behaviors.withTimers[Tick.type] { timer ⇒
        timer.startSingleTimer("T", Tick, 10.millis)
        Behaviors.receiveMessage { _ ⇒
          probe.ref ! Tock
          Behaviors.same
        }
      }

      spawn(behavior)

      manualTime.expectNoMessageFor(9.millis, probe)

      manualTime.timePasses(2.millis)
      probe.expectMessage(Tock)

      manualTime.expectNoMessageFor(10.seconds, probe)
    }
  }
}
Java

import akka.actor.typed.Behavior; import akka.actor.testkit.typed.javadsl.ManualTime; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import org.junit.ClassRule; import org.scalatest.junit.JUnitSuite; import java.time.Duration; import akka.actor.typed.javadsl.Behaviors; import org.junit.Test; import akka.actor.testkit.typed.javadsl.TestProbe; public class ManualTimerExampleTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(ManualTime.config()); private final ManualTime manualTime = ManualTime.get(testKit.system()); static final class Tick {} static final class Tock {} @Test public void testScheduleNonRepeatedTicks() { TestProbe<Tock> probe = testKit.createTestProbe(); Behavior<Tick> behavior = Behaviors.withTimers(timer -> { timer.startSingleTimer("T", new Tick(), Duration.ofMillis(10)); return Behaviors.receive( (context, tick) -> { probe.ref().tell(new Tock()); return Behaviors.same(); }); }); testKit.spawn(behavior); manualTime.expectNoMessageFor(Duration.ofMillis(9), probe); manualTime.timePasses(Duration.ofMillis(2)); probe.expectMessageClass(Tock.class); manualTime.expectNoMessageFor(Duration.ofSeconds(10), probe); } }
Found an error in this documentation? The source code for this page can be found here. Please feel free to edit and contribute a pull request.