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 BehaviorBehaviors 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 FutureCompletionStage or other asynchronous task and you rely on a callback to complete before observing the effect you want to test.
sourceobject Hello{sealed trait CommandcaseobjectCreateAnonymousChildextendsCommandcaseclassCreateChild(childName:String)extendsCommandcaseclassSayHelloToChild(childName:String)extendsCommandcaseobjectSayHelloToAnonymousChildextendsCommandcaseclassSayHello(who:ActorRef[String])extendsCommandcaseclassLogAndSayHello(who:ActorRef[String])extendsCommandcaseclassAskAQuestion(who:ActorRef[Question])extendsCommandcaseclassGotAnAnswer(answer:String,from:ActorRef[Question])extendsCommandcaseclassNoAnswerFrom(whom:ActorRef[Question])extendsCommanddef 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
case(context,AskAQuestion(who))=>implicit val timeout:Timeout=10.seconds
context.ask[Question,Answer](who,Question("do you know who I am?", _)){caseSuccess(answer)=>GotAnAnswer(answer.a, who)caseFailure(_)=>NoAnswerFrom(who)}Behaviors.same
case(context,GotAnAnswer(answer,from))=>
context.log.info("Got an answer [{}] from {}", answer,from)Behaviors.same
case(context,NoAnswerFrom(from))=>
context.log.info("Did not get an answer from {}",from)Behaviors.same
}// Included in Hello for brevitycaseclassQuestion(q:String, replyTo:ActorRef[Answer])caseclassAnswer(a:String)}
All of the tests make use of the BehaviorTestKitBehaviorTestKit to avoid the need for a real ActorContext. Some of the tests make use of the TestInboxTestInbox which allows the creation of an ActorRefActorRef that can be used for synchronous testing, similar to the TestProbe used for asynchronous testing.
sourceBehaviorTestKit<Hello.Command> test =BehaviorTestKit.create(Hello.create());
test.run(Hello.CreateAnAnonymousChild.INSTANCE);
test.expectEffectClass(Effect.SpawnedAnonymous.class);
Sending messages
For testing sending a message a TestInboxTestInbox is created that provides an ActorRefActorRef and methods to assert against the messages that have been sent to it.
sourceBehaviorTestKit<Hello.Command> testKit =BehaviorTestKit.create(Hello.create());
testKit.run(Hello.SayHelloToAnonymousChild.INSTANCE);// Anonymous actors are created as: $a $b etcTestInbox<String> childInbox = testKit.childInbox("$a");
childInbox.expectMessage("hello stranger");
An ask via ActorContext can be tested with the assistance of the Effect.AskInitiatedEffect.AskInitiatedEffect. The request message is sent to the target recipient and can be obtained from the AskInitiated. The interaction may be completed by calling respondWith or timeout on the AskInitiated, and the transformation of the response or timeout into the requestor’s protocol may also be tested using the adaptResponse or adaptTimeout methods.
sourceval testKit =BehaviorTestKit(Hello())
val askee =TestInbox[Hello.Question]()
testKit.run(Hello.AskAQuestion(askee.ref))// The ask message is sent and can be inspected via the TestInbox// note that the "replyTo" address is not directly predictable
val question = askee.receiveMessage()// The particulars of the `context.ask` call are captured as an Effect
val effect = testKit.expectEffectType[AskInitiated[Hello.Question,Hello.Answer,Hello.Command]]
testKit.clearLog()// The returned effect can be used to complete or time-out the ask at most once
effect.respondWith(Hello.Answer("I think I met you somewhere, sometime"))// (since we completed the ask, timing out is commented out)// effect.timeout()// Completing/timing-out the ask is processed synchronously
testKit.logEntries().size shouldBe 1// The message (including the synthesized "replyTo" address) can be inspected from the effect
val sentQuestion = effect.askMessage
// The response adaptation can be tested as many times as you want without completing the ask
val response1 = effect.adaptResponse(Hello.Answer("No. Who are you?"))
val response2 = effect.adaptResponse(Hello.Answer("Hey Joe!"))// ... as can the message sent on a timeout
val timeoutResponse = effect.adaptTimeout
// The response timeout can be inspected
val responseTimeout = effect.responseTimeout
sourceBehaviorTestKit<Hello.Command> test =BehaviorTestKit.create(Hello.create());TestInbox<Hello.Question> askee =TestInbox.create();
test.run(newHello.AskAQuestion(askee.getRef()));Hello.Question question = askee.receiveMessage();// Note that the replyTo address in the message is not a priori predictable, so shouldn't be// asserted against
assertEquals(question.q,"do you know who I am?");// Retrieve a description of the performed ask@SuppressWarnings("unchecked")Effect.AskInitiated<Hello.Question,Hello.Answer,Hello.Command> effect =
test.expectEffectClass(Effect.AskInitiated.class);
test.clearLog();// The effect can be used to complete or time-out the ask at most once
effect.respondWith(newHello.Answer("I think I met you somewhere, sometime"));// commented out because we've completed the ask// effect.timeout();// Completing/timing-out the ask is processed synchronouslyList<CapturedLogEvent> allLogEntries = test.getAllLogEntries();
assertEquals(allLogEntries.size(),1);// The message, including the synthesized "replyTo", can be inspected from the effect
assertEquals(question, effect.askMessage());// The response adaptation can be tested as many times as you want without completing the askHello.Command response1 = effect.adaptResponse(newHello.Answer("No. Who are you?"));
assertEquals(((Hello.GotAnAnswer) response1).answer,"No. Who are you?");// ... as can the message sent on a timeout
assertTrue(effect.adaptTimeout()instanceofHello.NoAnswerFrom);// The response timeout is captured
assertEquals(effect.responseTimeout().toSeconds(),10L);
The BehaviorTestKitBehaviorTestKit 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: