Testing

Dependency

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

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

Introduction

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

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.

Note

This module is ready to be used in production, but it is still marked as may change. This means that API or semantics can change without warning or deprecation period, but such changes will be collected and be performed in Akka 2.6.0 rather than in 2.5.x patch releases.

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
sourceimport akka.actor.testkit.typed.CapturedLogEvent
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._
import akka.event.Logging
Java
sourceimport akka.actor.testkit.typed.CapturedLogEvent;
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.*;
import akka.event.Logging;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;

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

Scala
sourcesealed 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
case class LogAndSayHello(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
  case (context, LogAndSayHello(who)) =>
    context.log.info("Saying hello to {}", who.path.name)
    who ! "hello"
    Behaviors.same
}
Java
sourceinterface 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 class LogAndSayHello implements Command {
  private final ActorRef<String> who;

  public LogAndSayHello(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();
            })
        .onMessage(
            LogAndSayHello.class,
            (context, message) -> {
              context.getLog().info("Saying hello to {}", message.who.path().name());
              message.who.tell("hello");
              return Behaviors.same();
            })
        .build();

For creating a child actor a noop actor is created:

Scala
sourceval childActor = Behaviors.receiveMessage[String] { _ =>
  Behaviors.same[String]
}
Java
sourcepublic 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
sourceval testKit = BehaviorTestKit(myBehavior)
testKit.run(CreateChild("child"))
testKit.expectEffect(Spawned(childActor, "child"))
Java
sourceBehaviorTestKit<Command> test = BehaviorTestKit.create(myBehavior);
test.run(new CreateAChild("child"));
test.expectEffect(Effects.spawned(childActor, "child", Props.empty()));

Anonymously:

Scala
sourceval testKit = BehaviorTestKit(myBehavior)
testKit.run(CreateAnonymousChild)
testKit.expectEffect(SpawnedAnonymous(childActor))
Java
sourceBehaviorTestKit<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
sourceval testKit = BehaviorTestKit(myBehavior)
val inbox = TestInbox[String]()
testKit.run(SayHello(inbox.ref))
inbox.expectMessage("hello")
Java
sourceBehaviorTestKit<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
sourceval testKit = BehaviorTestKit(myBehavior)
testKit.run(SayHelloToChild("child"))
val childInbox = testKit.childInbox[String]("child")
childInbox.expectMessage("hello")
Java
sourceBehaviorTestKit<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
sourceval testKit = BehaviorTestKit(myBehavior)
testKit.run(SayHelloToAnonymousChild)
val child = testKit.expectEffectType[SpawnedAnonymous[String]]

val childInbox = testKit.childInbox(child.ref)
childInbox.expectMessage("hello stranger")
Java
sourceBehaviorTestKit<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

Checking for Log Messages

The BehaviorTestkit also keeps track of everything that is being logged. Here, you can see an example on how to check if the behavior logged certain messages:

Scala
sourceval testKit = BehaviorTestKit(myBehavior)
val inbox = TestInbox[String]("Inboxer")
testKit.run(LogAndSayHello(inbox.ref))

testKit.logEntries() shouldBe Seq(CapturedLogEvent(Logging.InfoLevel, "Saying hello to Inboxer"))
Java
sourceBehaviorTestKit<Command> test = BehaviorTestKit.create(myBehavior);
TestInbox<String> inbox = TestInbox.create("Inboxer");
test.run(new LogAndSayHello(inbox.getRef()));

List<CapturedLogEvent> allLogEntries = test.getAllLogEntries();
assertEquals(1, allLogEntries.size());
CapturedLogEvent expectedLogEvent =
    new CapturedLogEvent(
        Logging.InfoLevel(),
        "Saying hello to Inboxer",
        Optional.empty(),
        Optional.empty(),
        new HashMap<>());
assertEquals(expectedLogEvent, allLogEntries.get(0));

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
sourcecase 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
sourcepublic 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;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Pong)) return false;
    Pong pong = (Pong) o;
    return message.equals(pong.message);
  }

  @Override
  public int hashCode() {
    return Objects.hash(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
sourceclass AsyncTestingExampleSpec extends WordSpec with BeforeAndAfterAll with Matchers {
  val testKit = ActorTestKit()
Java
sourcepublic class AsyncTestingExampleTest extends JUnitSuite {
  static final 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
sourceoverride def afterAll(): Unit = testKit.shutdownTestKit()
Java
source@AfterClass
public static 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
sourceval pinger = testKit.spawn(echoActor, "ping")
val probe = testKit.createTestProbe[Pong]()
pinger ! Ping("hello", probe.ref)
probe.expectMessage(Pong("hello"))
Java
sourceActorRef<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
sourceval pinger = testKit.spawn(echoActor)
Java
sourceActorRef<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.

Stopping actors

The method will wait until the actor stops or throw an assertion error in case of a timeout.

Scala
sourceval pinger1 = testKit.spawn(echoActor, "pinger")
pinger1 ! Ping("hello", probe.ref)
probe.expectMessage(Pong("hello"))
testKit.stop(pinger1) // Uses default timeout

// Immediately creating an actor with the same name
val pinger2 = testKit.spawn(echoActor, "pinger")
pinger2 ! Ping("hello", probe.ref)
probe.expectMessage(Pong("hello"))
testKit.stop(pinger2, 10.seconds) // Custom timeout
Java
sourceActorRef<Ping> pinger1 = testKit.spawn(echoActor, "pinger");
pinger1.tell(new Ping("hello", probe.ref()));
probe.expectMessage(new Pong("hello"));
testKit.stop(pinger1);

// Immediately creating an actor with the same name
ActorRef<Ping> pinger2 = testKit.spawn(echoActor, "pinger");
pinger2.tell(new Ping("hello", probe.ref()));
probe.expectMessage(new Pong("hello"));
testKit.stop(pinger2, Duration.ofSeconds(10));

The stop method can only be used for actors that were spawned by the same ActorTestKit. Other actors will not be stopped by that method.

Observing mocked behavior

When testing a component (which may be an actor or not) that interacts with other actors it can be useful to not have to run the other actors it depends on. Instead, you might want to create mock behaviors that accept and possibly respond to messages in the same way the other actor would do but without executing any actual logic. In addition to this it can also be useful to observe those interactions to assert that the component under test did send the expected messages. This allows the same kinds of tests as untyped TestActor/Autopilot.

As an example, let’s assume we’d like to test the following component:

Scala
sourcecase class Message(i: Int, replyTo: ActorRef[Try[Int]])

class Producer(publisher: ActorRef[Message])(implicit scheduler: Scheduler) {

  def produce(messages: Int)(implicit timeout: Timeout): Unit = {
    (0 until messages).foreach(publish)
  }

  private def publish(i: Int)(implicit timeout: Timeout): Future[Try[Int]] = {
    publisher.ask(ref => Message(i, ref))
  }

}
Java
source
static class Message { int i; ActorRef<Try<Integer>> replyTo; Message(int i, ActorRef<Try<Integer>> replyTo) { this.i = i; this.replyTo = replyTo; } } public static class Producer { private Scheduler scheduler; private ActorRef<Message> publisher; Producer(Scheduler scheduler, ActorRef<Message> publisher) { this.scheduler = scheduler; this.publisher = publisher; } public void produce(int messages) { IntStream.range(0, messages).forEach(this::publish); } private CompletionStage<Try<Integer>> publish(int i) { return AskPattern.ask( publisher, (ActorRef<Try<Integer>> ref) -> new Message(i, ref), Duration.ofSeconds(3), scheduler); } }

In our test, we create a mocked publisher actor. Additionally we use Behaviors.monitor with a TestProbe in order to be able to verify the interaction of the producer with the publisher:

Scala
sourceimport testKit._

// simulate the happy path
val mockedBehavior = Behaviors.receiveMessage[Message] { msg =>
  msg.replyTo ! Success(msg.i)
  Behaviors.same
}
val probe = testKit.createTestProbe[Message]()
val mockedPublisher = testKit.spawn(Behaviors.monitor(probe.ref, mockedBehavior))

// test our component
val producer = new Producer(mockedPublisher)
val messages = 3
producer.produce(messages)

// verify expected behavior
for (i <- 0 until messages) {
  val msg = probe.expectMessageType[Message]
  msg.i shouldBe i
}
Java
source// simulate the happy path
Behavior<Message> mockedBehavior =
    Behaviors.receiveMessage(
        message -> {
          message.replyTo.tell(new Success<>(message.i));
          return Behaviors.same();
        });
TestProbe<Message> probe = testKit.createTestProbe();
ActorRef<Message> mockedPublisher =
    testKit.spawn(Behaviors.monitor(probe.ref(), mockedBehavior));

// test our component
Producer producer = new Producer(testKit.scheduler(), mockedPublisher);
int messages = 3;
producer.produce(messages);

// verify expected behavior
IntStream.range(0, messages)
    .forEach(
        i -> {
          Message msg = probe.expectMessageClass(Message.class);
          assertEquals(i, msg.i);
        });

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
sourceimport 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
sourceimport 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
sourceimport 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
source
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.