Synchronous behavior testing
You are viewing the documentation for the new actor APIs, to view the Akka Classic documentation, see Classic Testing.
The BehaviorTestKit
provides a very nice way of unit testing a Behavior
in a deterministic way, but it has some limitations to be aware of.
Certain Behavior
s will be hard to test synchronously and the BehaviorTestKit
doesn’t support testing of all features. In those cases the asynchronous ActorTestKit is recommended. Example of limitations:
- Spawning of
Future
or other asynchronous task and you rely on a callback to complete before observing the effect you want to test. - Usage of scheduler is not supported.
EventSourcedBehavior
can’t be tested.- Interactions with other actors must be stubbed.
- Blackbox testing style.
- Supervision is not supported.
The BehaviorTestKit
will be improved and some of these problems will be removed but it will always have limitations.
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
-
source
import 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 com.typesafe.config.ConfigFactory import org.slf4j.event.Level
- Java
Each of the tests are testing an actor that based on the message executes a different effect to be tested:
- Scala
-
source
object Hello { sealed trait Command case object CreateAnonymousChild extends Command case class CreateChild(childName: String) extends Command case class SayHelloToChild(childName: String) extends Command case object SayHelloToAnonymousChild extends Command case class SayHello(who: ActorRef[String]) extends Command case class LogAndSayHello(who: ActorRef[String]) extends Command def apply(): Behaviors.Receive[Command] = Behaviors.receivePartial { 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
For creating a child actor a noop actor is created:
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
-
source
val testKit = BehaviorTestKit(Hello()) testKit.run(Hello.CreateChild("child")) testKit.expectEffect(Spawned(childActor, "child"))
- Java
Anonymously:
- Scala
-
source
val testKit = BehaviorTestKit(Hello()) testKit.run(Hello.CreateAnonymousChild) testKit.expectEffect(SpawnedAnonymous(childActor))
- Java
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
-
source
val testKit = BehaviorTestKit(Hello()) val inbox = TestInbox[String]() testKit.run(Hello.SayHello(inbox.ref)) inbox.expectMessage("hello")
- Java
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
-
source
val testKit = BehaviorTestKit(Hello()) testKit.run(Hello.SayHelloToChild("child")) val childInbox = testKit.childInbox[String]("child") childInbox.expectMessage("hello")
- Java
For anonymous children the actor names are generated in a deterministic way:
- Scala
-
source
val testKit = BehaviorTestKit(Hello()) testKit.run(Hello.SayHelloToAnonymousChild) val child = testKit.expectEffectType[SpawnedAnonymous[String]] val childInbox = testKit.childInbox(child.ref) childInbox.expectMessage("hello stranger")
- Java
Testing other effects
The BehaviorTestKit
keeps track other effects you can verify, look at the sub-classes of Effect
- SpawnedAdapter
- Stopped
- Watched
- WatchedWith
- Unwatched
- Scheduled
- TimerScheduled
- TimerCancelled
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
-
source
val testKit = BehaviorTestKit(Hello()) val inbox = TestInbox[String]("Inboxer") testKit.run(Hello.LogAndSayHello(inbox.ref)) testKit.logEntries() shouldBe Seq(CapturedLogEvent(Level.INFO, "Saying hello to Inboxer"))
- Java
See the other public methods and API documentation on BehaviorTestKit
for other types of verification.