From 3926ffa954412ee09e81a78b6da8343441ce9b1f Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 7 Oct 2019 16:51:25 +0200 Subject: [PATCH] doc: Add page for testing of EventSourcedBehavior, #27899 (#27902) --- .../sharding/typed/AccountExampleDocTest.java | 100 ++++++++++++++++++ .../typed/AccountExampleDocSpec.scala | 81 ++++++++++++++ .../main/paradox/typed/persistence-testing.md | 65 ++++++++++++ .../src/main/paradox/typed/persistence.md | 3 +- akka-docs/src/main/paradox/typed/testing.md | 2 +- 5 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleDocTest.java create mode 100644 akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleDocSpec.scala create mode 100644 akka-docs/src/main/paradox/typed/persistence-testing.md diff --git a/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleDocTest.java b/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleDocTest.java new file mode 100644 index 0000000000..564a020c8d --- /dev/null +++ b/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleDocTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2018-2019 Lightbend Inc. + */ + +package jdocs.akka.cluster.sharding.typed; + +// #test +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; + +// #test + +import org.scalatest.junit.JUnitSuite; + +import static jdocs.akka.cluster.sharding.typed.AccountExampleWithEventHandlersInState.AccountEntity; + +// #test +public class AccountExampleDocTest + // #test + extends JUnitSuite +// #test +{ + + // #inmem-config + private static final String inmemConfig = + "akka.persistence.journal.plugin = \"akka.persistence.journal.inmem\" \n"; + // #inmem-config + + // #snapshot-store-config + 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"; + // #snapshot-store-config + + 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 ref = + testKit.spawn(AccountEntity.create("1", PersistenceId.of("Account", "1"))); + TestProbe 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 ref = + testKit.spawn(AccountEntity.create("2", PersistenceId.of("Account", "2"))); + TestProbe 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 ref = + testKit.spawn(AccountEntity.create("3", PersistenceId.of("Account", "3"))); + TestProbe 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 getProbe = + testKit.createTestProbe(AccountEntity.CurrentBalance.class); + ref.tell(new AccountEntity.GetBalance(getProbe.getRef())); + assertEquals( + BigDecimal.valueOf(100), + getProbe.expectMessageClass(AccountEntity.CurrentBalance.class).balance); + } +} +// #test diff --git a/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleDocSpec.scala b/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleDocSpec.scala new file mode 100644 index 0000000000..bd2f49102b --- /dev/null +++ b/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleDocSpec.scala @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017-2019 Lightbend Inc. + */ + +package docs.akka.cluster.sharding.typed + +//#test +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 + +//#test + +import docs.akka.cluster.sharding.typed.AccountExampleWithEventHandlersInState.AccountEntity + +object AccountExampleDocSpec { + val inmemConfig = + //#inmem-config + """ + akka.persistence.journal.plugin = "akka.persistence.journal.inmem" + """ + //#inmem-config + + val snapshotConfig = + //#snapshot-store-config + s""" + akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local" + akka.persistence.snapshot-store.local.dir = "target/snapshot-${UUID.randomUUID().toString}" + """ + //#snapshot-store-config +} + +//#test +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)) + } + } +} +//#test diff --git a/akka-docs/src/main/paradox/typed/persistence-testing.md b/akka-docs/src/main/paradox/typed/persistence-testing.md new file mode 100644 index 0000000000..782e214cc2 --- /dev/null +++ b/akka-docs/src/main/paradox/typed/persistence-testing.md @@ -0,0 +1,65 @@ +# Testing + +## Dependency + +To use Akka Persistence and Actor TestKit, add the module to your project: + +@@dependency[sbt,Maven,Gradle] { + group1=com.typesafe.akka + artifact1=akka-persistence-typed_$scala.binary_version$ + version1=$akka.version$ + group2=com.typesafe.akka + artifact2=akka-actor-testkit-typed_$scala.binary_version$ + version2=$akka.version$ + scope2=test +} + +## Unit testing + +Unit testing of `EventSourcedBehavior` can be done with the @ref:[ActorTestKit](testing-async.md) +in the same way as other behaviors. + +@ref:[Synchronous behavior testing](testing-sync.md) for `EventSourcedBehavior` is not supported yet, but +tracked in @github[issue #26338](#23712). + +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 @scala[`ScalaTestWithActorTestKit`]@java[`TestKitJunitResource`]. + +Scala +: @@snip [AccountExampleDocSpec.scala](/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleDocSpec.scala) { #inmem-config } + +Java +: @@snip [AccountExampleDocTest.java](/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleDocTest.java) { #inmem-config } + +Optionally you can also configure a snapshot store. To enable the file based snapshot store you need to pass the +following configuration to the @scala[`ScalaTestWithActorTestKit`]@java[`TestKitJunitResource`]. + +Scala +: @@snip [AccountExampleDocSpec.scala](/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleDocSpec.scala) { #snapshot-store-config } + +Java +: @@snip [AccountExampleDocTest.java](/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleDocTest.java) { #snapshot-store-config } + +Then you can `spawn` the `EventSourcedBehavior` and verify the outcome of sending commands to the actor using +the facilities of the @ref:[ActorTestKit](testing-async.md). + +A full test for the `AccountEntity`, which is shown in the @ref:[Style Guide](style-guide.md), may look like this: + +Scala +: @@snip [AccountExampleDocSpec.scala](/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleDocSpec.scala) { #test } + +Java +: @@snip [AccountExampleDocTest.java](/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleDocTest.java) { #test } + +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 @ref:[Persistence Plugin Proxy](../persistence-plugins.md#persistence-plugin-proxy) +it's often better and more realistic to use a real database. + +See [akka-samples issue #128](https://github.com/akka/akka-samples/issues/128). diff --git a/akka-docs/src/main/paradox/typed/persistence.md b/akka-docs/src/main/paradox/typed/persistence.md index bbef8fd784..d46b38c590 100644 --- a/akka-docs/src/main/paradox/typed/persistence.md +++ b/akka-docs/src/main/paradox/typed/persistence.md @@ -7,6 +7,7 @@ project.description: Event Sourcing with Akka Persistence enables actors to pers * [Persistence coding style](persistence-style.md) * [Persistence snapshotting](persistence-snapshot.md) +* [Persistence testing](persistence-testing.md) * [Persistence schema evolution](../persistence-schema-evolution.md) * [Persistence query](../persistence-query.md) * [Persistence query LevelDB](../persistence-query-leveldb.md) @@ -23,7 +24,7 @@ For the Akka Classic documentation of this feature see @ref:[Classic Akka Persis ## Dependency -To use Akka Persistence Typed, add the module to your project: +To use Akka Persistence, add the module to your project: @@dependency[sbt,Maven,Gradle] { group=com.typesafe.akka diff --git a/akka-docs/src/main/paradox/typed/testing.md b/akka-docs/src/main/paradox/typed/testing.md index 93752e74e1..62959ef6e1 100644 --- a/akka-docs/src/main/paradox/typed/testing.md +++ b/akka-docs/src/main/paradox/typed/testing.md @@ -8,7 +8,7 @@ For the Akka Classic documentation of this feature see @ref:[Classic Testing](.. ## Dependency -To use Akka TestKit add the module to your project: +To use Actor TestKit add the module to your project: @@dependency[sbt,Maven,Gradle] { group=com.typesafe.akka