Testing

Dependency

To use Akka Persistence and Actor TestKit, add the module to your project:

sbt
libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-persistence-typed" % "2.6.1",
  "com.typesafe.akka" %% "akka-actor-testkit-typed" % "2.6.1" % Test
)
Maven
<dependency>
  <groupId>com.typesafe.akka</groupId>
  <artifactId>akka-persistence-typed_2.13</artifactId>
  <version>2.6.1</version>
</dependency>
<dependency>
  <groupId>com.typesafe.akka</groupId>
  <artifactId>akka-actor-testkit-typed_2.13</artifactId>
  <version>2.6.1</version>
  <scope>test</scope>
</dependency>
Gradle
dependencies {
  compile group: 'com.typesafe.akka', name: 'akka-persistence-typed_2.13', version: '2.6.1',
  test group: 'com.typesafe.akka', name: 'akka-actor-testkit-typed_2.13', version: '2.6.1'
}

Unit testing

Unit testing of EventSourcedBehavior can be done with the ActorTestKit in the same way as other behaviors.

Synchronous behavior testing for EventSourcedBehavior is not supported yet, but tracked in issue #26338.

You need to configure a journal, and the in-memory journal is sufficient for unit testing. To enable the in-memory journal you need to pass the following configuration to the ScalaTestWithActorTestKitTestKitJunitResource.

Scala
""" 
akka.persistence.journal.plugin = "akka.persistence.journal.inmem"
"""
Java
private static final String inmemConfig =
    "akka.persistence.journal.plugin = \"akka.persistence.journal.inmem\" \n";

Optionally you can also configure a snapshot store. To enable the file based snapshot store you need to pass the following configuration to the ScalaTestWithActorTestKitTestKitJunitResource.

Scala
s""" 
akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local"
akka.persistence.snapshot-store.local.dir = "target/snapshot-${UUID.randomUUID().toString}"
"""
Java
private static final String snapshotConfig =
    "akka.persistence.snapshot-store.plugin = \"akka.persistence.snapshot-store.local\" \n"
        + "akka.persistence.snapshot-store.local.dir = \"target/snapshot-"
        + UUID.randomUUID().toString()
        + "\" \n";

Then you can spawn the EventSourcedBehavior and verify the outcome of sending commands to the actor using the facilities of the ActorTestKit.

A full test for the AccountEntity, which is shown in the Persistence Style Guide, may look like this:

Scala
import java.util.UUID

import akka.actor.testkit.typed.scaladsl.LogCapturing
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
import akka.persistence.typed.PersistenceId
import org.scalatest.WordSpecLike

class AccountExampleDocSpec extends ScalaTestWithActorTestKit(s"""
      akka.persistence.journal.plugin = "akka.persistence.journal.inmem"
      akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local"
      akka.persistence.snapshot-store.local.dir = "target/snapshot-${UUID.randomUUID().toString}"
    """) with WordSpecLike with LogCapturing {

  "Account" must {

    "handle Withdraw" in {
      val probe = createTestProbe[AccountEntity.OperationResult]()
      val ref = spawn(AccountEntity("1", PersistenceId("Account", "1")))
      ref ! AccountEntity.CreateAccount(probe.ref)
      probe.expectMessage(AccountEntity.Confirmed)
      ref ! AccountEntity.Deposit(100, probe.ref)
      probe.expectMessage(AccountEntity.Confirmed)
      ref ! AccountEntity.Withdraw(10, probe.ref)
      probe.expectMessage(AccountEntity.Confirmed)
    }

    "reject Withdraw overdraft" in {
      val probe = createTestProbe[AccountEntity.OperationResult]()
      val ref = spawn(AccountEntity("2", PersistenceId("Account", "2")))
      ref ! AccountEntity.CreateAccount(probe.ref)
      probe.expectMessage(AccountEntity.Confirmed)
      ref ! AccountEntity.Deposit(100, probe.ref)
      probe.expectMessage(AccountEntity.Confirmed)
      ref ! AccountEntity.Withdraw(110, probe.ref)
      probe.expectMessageType[AccountEntity.Rejected]
    }

    "handle GetBalance" in {
      val opProbe = createTestProbe[AccountEntity.OperationResult]()
      val ref = spawn(AccountEntity("3", PersistenceId("Account", "3")))
      ref ! AccountEntity.CreateAccount(opProbe.ref)
      opProbe.expectMessage(AccountEntity.Confirmed)
      ref ! AccountEntity.Deposit(100, opProbe.ref)
      opProbe.expectMessage(AccountEntity.Confirmed)

      val getProbe = createTestProbe[AccountEntity.CurrentBalance]()
      ref ! AccountEntity.GetBalance(getProbe.ref)
      getProbe.expectMessage(AccountEntity.CurrentBalance(100))
    }
  }
}
Java
import java.math.BigDecimal;
import java.util.UUID;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

import akka.actor.testkit.typed.javadsl.LogCapturing;
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
import akka.actor.testkit.typed.javadsl.TestProbe;
import akka.actor.typed.ActorRef;
import akka.persistence.typed.PersistenceId;

public class AccountExampleDocTest
{

  private static final String inmemConfig =
      "akka.persistence.journal.plugin = \"akka.persistence.journal.inmem\" \n";

  private static final String snapshotConfig =
      "akka.persistence.snapshot-store.plugin = \"akka.persistence.snapshot-store.local\" \n"
          + "akka.persistence.snapshot-store.local.dir = \"target/snapshot-"
          + UUID.randomUUID().toString()
          + "\" \n";

  private static final String config = inmemConfig + snapshotConfig;

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

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

  @Test
  public void handleWithdraw() {
    ActorRef<AccountEntity.Command> ref =
        testKit.spawn(AccountEntity.create("1", PersistenceId.of("Account", "1")));
    TestProbe<AccountEntity.OperationResult> probe =
        testKit.createTestProbe(AccountEntity.OperationResult.class);
    ref.tell(new AccountEntity.CreateAccount(probe.getRef()));
    probe.expectMessage(AccountEntity.Confirmed.INSTANCE);
    ref.tell(new AccountEntity.Deposit(BigDecimal.valueOf(100), probe.getRef()));
    probe.expectMessage(AccountEntity.Confirmed.INSTANCE);
    ref.tell(new AccountEntity.Withdraw(BigDecimal.valueOf(10), probe.getRef()));
    probe.expectMessage(AccountEntity.Confirmed.INSTANCE);
  }

  @Test
  public void rejectWithdrawOverdraft() {
    ActorRef<AccountEntity.Command> ref =
        testKit.spawn(AccountEntity.create("2", PersistenceId.of("Account", "2")));
    TestProbe<AccountEntity.OperationResult> probe =
        testKit.createTestProbe(AccountEntity.OperationResult.class);
    ref.tell(new AccountEntity.CreateAccount(probe.getRef()));
    probe.expectMessage(AccountEntity.Confirmed.INSTANCE);
    ref.tell(new AccountEntity.Deposit(BigDecimal.valueOf(100), probe.getRef()));
    probe.expectMessage(AccountEntity.Confirmed.INSTANCE);
    ref.tell(new AccountEntity.Withdraw(BigDecimal.valueOf(110), probe.getRef()));
    probe.expectMessageClass(AccountEntity.Rejected.class);
  }

  @Test
  public void handleGetBalance() {
    ActorRef<AccountEntity.Command> ref =
        testKit.spawn(AccountEntity.create("3", PersistenceId.of("Account", "3")));
    TestProbe<AccountEntity.OperationResult> opProbe =
        testKit.createTestProbe(AccountEntity.OperationResult.class);
    ref.tell(new AccountEntity.CreateAccount(opProbe.getRef()));
    opProbe.expectMessage(AccountEntity.Confirmed.INSTANCE);
    ref.tell(new AccountEntity.Deposit(BigDecimal.valueOf(100), opProbe.getRef()));
    opProbe.expectMessage(AccountEntity.Confirmed.INSTANCE);

    TestProbe<AccountEntity.CurrentBalance> getProbe =
        testKit.createTestProbe(AccountEntity.CurrentBalance.class);
    ref.tell(new AccountEntity.GetBalance(getProbe.getRef()));
    assertEquals(
        BigDecimal.valueOf(100),
        getProbe.expectMessageClass(AccountEntity.CurrentBalance.class).balance);
  }
}

Note that each test case is using a different PersistenceId to not interfere with each other.

Integration testing

The in-memory journal and file based snapshot store can be used also for integration style testing of a single ActorSystem, for example when using Cluster Sharding with a single Cluster node.

For tests that involve more than one Cluster node you have to use another journal and snapshot store. While it’s possible to use the Persistence Plugin Proxy it’s often better and more realistic to use a real database.

See akka-samples issue #128.

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.