Initial pass of typed testkits + documenation (#24187)

* Initial pass of typed testkits

Not expecting this to be the final API just want to get some
examples documented and internals hidden

Refs #23667 and #22764
This commit is contained in:
Christopher Batey 2017-12-22 16:44:39 +00:00 committed by GitHub
parent 2aab0762dd
commit 7aa831bc2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 1025 additions and 402 deletions

View file

@ -0,0 +1,62 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package jdocs.akka.typed.testing.async;
import akka.actor.typed.ActorRef;
import akka.actor.typed.ActorSystem;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.Actor;
import akka.testkit.typed.javadsl.TestProbe;
import akka.testkit.typed.TestKit;
import org.junit.AfterClass;
import org.junit.Test;
//#test-header
public class BasicAsyncTestingTest extends TestKit {
public BasicAsyncTestingTest() {
super(ActorSystem.create(Actor.empty(), "BasicAsyncTestingTest"));
}
//#test-header
//#under-test
public static class Ping {
private String msg;
private ActorRef<Pong> replyTo;
public Ping(String msg, ActorRef<Pong> replyTo) {
this.msg = msg;
this.replyTo = replyTo;
}
}
public static class Pong {
private String msg;
public Pong(String msg) {
this.msg = msg;
}
}
Behavior<Ping> echoActor = Actor.immutable((ctx, ping) -> {
ping.replyTo.tell(new Pong(ping.msg));
return Actor.same();
});
//#under-test
//#test-shutdown
@AfterClass
public void cleanup() {
this.shutdown();
}
//#test-shutdown
@Test
public void testVerifyingAResponse() {
//#test
TestProbe<Pong> probe = new TestProbe<>(system(), testkitSettings());
ActorRef<Ping> pinger = actorOf(echoActor, "ping");
pinger.tell(new Ping("hello", probe.ref()));
probe.expectMsg(new Pong("hello"));
//#test
}
}

View file

@ -0,0 +1,114 @@
package jdocs.akka.typed.testing.sync;
//#imports
import akka.actor.typed.*;
import akka.actor.typed.javadsl.*;
import akka.testkit.typed.*;
//#imports
import org.junit.Test;
import org.scalatest.junit.JUnitSuite;
public class BasicSyncTestingTest extends JUnitSuite {
//#child
public static Behavior<String> childActor = Actor.immutable((ctx, msg) -> Actor.same());
//#child
//#under-test
interface 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 Behavior<Command> myBehaviour = Actor.immutable(Command.class)
.onMessage(CreateAChild.class, (ctx, msg) -> {
ctx.spawn(childActor, msg.childName);
return Actor.same();
})
.onMessage(CreateAnAnonymousChild.class, (ctx, msg) -> {
ctx.spawnAnonymous(childActor);
return Actor.same();
})
.onMessage(SayHelloToChild.class, (ctx, msg) -> {
ActorRef<String> child = ctx.spawn(childActor, msg.childName);
child.tell("hello");
return Actor.same();
})
.onMessage(SayHelloToAnonymousChild.class, (ctx, msg) -> {
ActorRef<String> child = ctx.spawnAnonymous(childActor);
child.tell("hello stranger");
return Actor.same();
}).onMessage(SayHello.class, (ctx, msg) -> {
msg.who.tell("hello");
return Actor.same();
}).build();
//#under-test
@Test
public void testSpawning() {
//#test-child
BehaviorTestkit<Command> test = BehaviorTestkit.create(myBehaviour);
test.run(new CreateAChild("child"));
test.expectEffect(new Effect.Spawned(childActor, "child", Props.empty()));
//#test-child
}
@Test
public void testSpawningAnonymous() {
//#test-anonymous-child
BehaviorTestkit<Command> test = BehaviorTestkit.create(myBehaviour);
test.run(new CreateAnAnonymousChild());
test.expectEffect(new Effect.SpawnedAnonymous(childActor, Props.empty()));
//#test-anonymous-child
}
@Test
public void testRecodingMessageSend() {
//#test-message
BehaviorTestkit<Command> test = BehaviorTestkit.create(myBehaviour);
TestInbox<String> inbox = new TestInbox<String>();
test.run(new SayHello(inbox.ref()));
inbox.expectMsg("hello");
//#test-message
}
@Test
public void testMessageToChild() {
//#test-child-message
BehaviorTestkit<Command> testKit = BehaviorTestkit.create(myBehaviour);
testKit.run(new SayHelloToChild("child"));
TestInbox<String> childInbox = testKit.childInbox("child");
childInbox.expectMsg("hello");
//#test-child-message
}
@Test
public void testMessageToAnonymousChild() {
//#test-child-message-anonymous
BehaviorTestkit<Command> testKit = BehaviorTestkit.create(myBehaviour);
testKit.run(new SayHelloToAnonymousChild());
// Anonymous actors are created as: $a $b etc
TestInbox<String> childInbox = testKit.childInbox("$a");
childInbox.expectMsg("hello stranger");
//#test-child-message-anonymous
}
}

View file

@ -266,7 +266,7 @@ abstract class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
| lifecycle = off
| autoreceive = off
| }
| typed.loggers = ["akka.typed.testkit.TestEventListener"]
| typed.loggers = ["akka.testkit.typed.TestEventListener"]
|}""".stripMargin)) {
import ActorContextSpec._

View file

@ -10,7 +10,7 @@ import akka.japi.pf.{ FI, PFBuilder }
import java.util.function.{ Function F1 }
import akka.Done
import akka.typed.testkit.{ EffectfulActorContext, Inbox }
import akka.testkit.typed.{ BehaviorTestkit, TestInbox }
object BehaviorSpec {
sealed trait Command {
@ -68,23 +68,23 @@ object BehaviorSpec {
def checkAux(signal: Signal, aux: Aux): Unit = ()
def checkAux(command: Command, aux: Aux): Unit = ()
case class Init(behv: Behavior[Command], inbox: Inbox[Event], aux: Aux) {
case class Init(behv: Behavior[Command], inbox: TestInbox[Event], aux: Aux) {
def mkCtx(): Setup = {
val ctx = new EffectfulActorContext("ctx", behv, 1000, system)
val msgs = inbox.receiveAll()
Setup(ctx, inbox, aux)
val testkit = BehaviorTestkit(behv)
inbox.receiveAll()
Setup(testkit, inbox, aux)
}
}
case class Setup(ctx: EffectfulActorContext[Command], inbox: Inbox[Event], aux: Aux)
case class Setup(testKit: BehaviorTestkit[Command], inbox: TestInbox[Event], aux: Aux)
def init(): Init = {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val (behv, aux) = behavior(inbox.ref)
Init(behv, inbox, aux)
}
def init(factory: ActorRef[Event] (Behavior[Command], Aux)): Init = {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val (behv, aux) = factory(inbox.ref)
Init(behv, inbox, aux)
}
@ -94,20 +94,20 @@ object BehaviorSpec {
implicit class Check(val setup: Setup) {
def check(signal: Signal): Setup = {
setup.ctx.signal(signal)
setup.testKit.signal(signal)
setup.inbox.receiveAll() should ===(GotSignal(signal) :: Nil)
checkAux(signal, setup.aux)
setup
}
def check(command: Command): Setup = {
setup.ctx.run(command)
setup.inbox.receiveAll() should ===(command.expectedResponse(setup.ctx))
setup.testKit.run(command)
setup.inbox.receiveAll() should ===(command.expectedResponse(setup.testKit.ctx))
checkAux(command, setup.aux)
setup
}
def check2(command: Command): Setup = {
setup.ctx.run(command)
val expected = command.expectedResponse(setup.ctx)
setup.testKit.run(command)
val expected = command.expectedResponse(setup.testKit.ctx)
setup.inbox.receiveAll() should ===(expected ++ expected)
checkAux(command, setup.aux)
setup
@ -118,7 +118,7 @@ object BehaviorSpec {
}
trait Siphon extends Common {
override type Aux = Inbox[Command]
override type Aux = TestInbox[Command]
override def checkAux(command: Command, aux: Aux): Unit = {
aux.receiveAll() should ===(command :: Nil)
@ -126,7 +126,7 @@ object BehaviorSpec {
}
trait SignalSiphon extends Common {
override type Aux = Inbox[Either[Signal, Command]]
override type Aux = TestInbox[Either[Signal, Command]]
override def checkAux(command: Command, aux: Aux): Unit = {
aux.receiveAll() should ===(Right(command) :: Nil)
@ -142,25 +142,25 @@ object BehaviorSpec {
case (ctx, GetSelf)
monitor ! Self(ctx.self)
SActor.same
case (ctx, Miss)
case (_, Miss)
monitor ! Missed
SActor.unhandled
case (ctx, Ignore)
case (_, Ignore)
monitor ! Ignored
SActor.same
case (ctx, Ping)
case (_, Ping)
monitor ! Pong
mkFull(monitor, state)
case (ctx, Swap)
case (_, Swap)
monitor ! Swapped
mkFull(monitor, state.next)
case (ctx, GetState())
case (_, GetState())
monitor ! state
SActor.same
case (ctx, Stop) SActor.stopped
case (_, _) SActor.unhandled
case (_, Stop) SActor.stopped
case (_, _) SActor.unhandled
} onSignal {
case (ctx, signal)
case (_, signal)
monitor ! GotSignal(signal)
SActor.same
}
@ -220,15 +220,15 @@ object BehaviorSpec {
}
"must react to Terminated" in {
mkCtx().check(Terminated(Inbox("x").ref)(null))
mkCtx().check(Terminated(TestInbox("x").ref)(null))
}
"must react to Terminated after a message" in {
mkCtx().check(GetSelf).check(Terminated(Inbox("x").ref)(null))
mkCtx().check(GetSelf).check(Terminated(TestInbox("x").ref)(null))
}
"must react to a message after Terminated" in {
mkCtx().check(Terminated(Inbox("x").ref)(null)).check(GetSelf)
mkCtx().check(Terminated(TestInbox("x").ref)(null)).check(GetSelf)
}
}
}
@ -250,10 +250,10 @@ object BehaviorSpec {
}
trait Unhandled extends Common {
"Unahndled" must {
"Unhandled" must {
"must return Unhandled" in {
val Setup(ctx, inbox, aux) = mkCtx()
Behavior.interpretMessage(ctx.currentBehavior, ctx, Miss) should be(Behavior.UnhandledBehavior)
val Setup(testKit, inbox, aux) = mkCtx()
Behavior.interpretMessage(testKit.currentBehavior, testKit.ctx, Miss) should be(Behavior.UnhandledBehavior)
inbox.receiveAll() should ===(Missed :: Nil)
checkAux(Miss, aux)
}
@ -263,16 +263,16 @@ object BehaviorSpec {
trait Stoppable extends Common {
"Stopping" must {
"must stop" in {
val Setup(ctx, inbox, aux) = mkCtx()
ctx.run(Stop)
ctx.currentBehavior should be(Behavior.StoppedBehavior)
val Setup(testkit, _, aux) = mkCtx()
testkit.run(Stop)
testkit.currentBehavior should be(Behavior.StoppedBehavior)
checkAux(Stop, aux)
}
}
}
trait Become extends Common with Unhandled {
private implicit val inbox = Inbox[State]("state")
private implicit val inbox = TestInbox[State]("state")
"Becoming" must {
"must be in state A" in {
@ -308,15 +308,15 @@ object BehaviorSpec {
}
"react to Terminated after swap" in {
mkCtx().check(Swap).check(Terminated(Inbox("x").ref)(null))
mkCtx().check(Swap).check(Terminated(TestInbox("x").ref)(null))
}
"react to Terminated after a message after swap" in {
mkCtx().check(Swap).check(GetSelf).check(Terminated(Inbox("x").ref)(null))
mkCtx().check(Swap).check(GetSelf).check(Terminated(TestInbox("x").ref)(null))
}
"react to a message after Terminated after swap" in {
mkCtx().check(Swap).check(Terminated(Inbox("x").ref)(null)).check(GetSelf)
mkCtx().check(Swap).check(Terminated(TestInbox("x").ref)(null)).check(GetSelf)
}
}
}
@ -368,7 +368,7 @@ class ImmutableBehaviorSpec extends TypedSpec with Messages with BecomeWithLifec
case (_, Stop) SActor.stopped
case (_, _: AuxPing) SActor.unhandled
} onSignal {
case (ctx, signal)
case (_, signal)
monitor ! GotSignal(signal)
SActor.same
}
@ -405,7 +405,7 @@ class ImmutableWithSignalScalaBehaviorSpec extends TypedSpec with Messages with
case _: AuxPing SActor.unhandled
}
} onSignal {
case (ctx, sig)
case (_, sig)
monitor ! GotSignal(sig)
SActor.same
}
@ -485,17 +485,17 @@ class WidenedScalaBehaviorSpec extends ImmutableWithSignalScalaBehaviorSpec with
import SActor.BehaviorDecorators
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Command]("widenedListener")
val inbox = TestInbox[Command]("widenedListener")
super.behavior(monitor)._1.widen[Command] { case c inbox.ref ! c; c } inbox
}
}
class DeferredScalaBehaviorSpec extends ImmutableWithSignalScalaBehaviorSpec {
override type Aux = Inbox[Done]
override type Aux = TestInbox[Done]
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Done]("deferredListener")
(SActor.deferred(ctx {
val inbox = TestInbox[Done]("deferredListener")
(SActor.deferred(_ {
inbox.ref ! Done
super.behavior(monitor)._1
}), inbox)
@ -507,7 +507,7 @@ class DeferredScalaBehaviorSpec extends ImmutableWithSignalScalaBehaviorSpec {
class TapScalaBehaviorSpec extends ImmutableWithSignalScalaBehaviorSpec with Reuse with SignalSiphon {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Either[Signal, Command]]("tapListener")
val inbox = TestInbox[Either[Signal, Command]]("tapListener")
(SActor.tap((_, msg) inbox.ref ! Right(msg), (_, sig) inbox.ref ! Left(sig), super.behavior(monitor)._1), inbox)
}
}
@ -544,7 +544,7 @@ class ImmutableWithSignalJavaBehaviorSpec extends TypedSpec with Messages with B
case Stop SActor.stopped
case _: AuxPing SActor.unhandled
}),
fs((ctx, sig) {
fs((_, sig) {
monitor ! GotSignal(sig)
SActor.same
}))
@ -582,7 +582,7 @@ class ImmutableJavaBehaviorSpec extends TypedSpec with Messages with Become with
class WidenedJavaBehaviorSpec extends ImmutableWithSignalJavaBehaviorSpec with Reuse with Siphon {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Command]("widenedListener")
val inbox = TestInbox[Command]("widenedListener")
JActor.widened(super.behavior(monitor)._1, pf(_.`match`(classOf[Command], fi(x {
inbox.ref ! x
x
@ -591,11 +591,11 @@ class WidenedJavaBehaviorSpec extends ImmutableWithSignalJavaBehaviorSpec with R
}
class DeferredJavaBehaviorSpec extends ImmutableWithSignalJavaBehaviorSpec {
override type Aux = Inbox[Done]
override type Aux = TestInbox[Done]
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Done]("deferredListener")
(JActor.deferred(df(ctx {
val inbox = TestInbox[Done]("deferredListener")
(JActor.deferred(df(_ {
inbox.ref ! Done
super.behavior(monitor)._1
})), inbox)
@ -607,7 +607,7 @@ class DeferredJavaBehaviorSpec extends ImmutableWithSignalJavaBehaviorSpec {
class TapJavaBehaviorSpec extends ImmutableWithSignalJavaBehaviorSpec with Reuse with SignalSiphon {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Either[Signal, Command]]("tapListener")
val inbox = TestInbox[Either[Signal, Command]]("tapListener")
(JActor.tap(
pc((_, msg) inbox.ref ! Right(msg)),
ps((_, sig) inbox.ref ! Left(sig)),

View file

@ -3,14 +3,12 @@
*/
package akka.actor.typed
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.util.control.NoStackTrace
import akka.actor.typed.scaladsl.Actor
import akka.actor.typed.scaladsl.Actor.BehaviorDecorators
import akka.actor.typed.scaladsl.AskPattern._
import akka.typed.testkit.{ EffectfulActorContext, Inbox, TestKitSettings }
import akka.typed.testkit.scaladsl._
import akka.testkit.typed.{ BehaviorTestkit, TestInbox, TestKitSettings }
import akka.testkit.typed.scaladsl._
object DeferredSpec {
sealed trait Command
@ -21,7 +19,7 @@ object DeferredSpec {
case object Started extends Event
def target(monitor: ActorRef[Event]): Behavior[Command] =
Actor.immutable((ctx, cmd) cmd match {
Actor.immutable((_, cmd) cmd match {
case Ping
monitor ! Pong
Actor.same
@ -92,7 +90,7 @@ class DeferredSpec extends TypedSpec with StartSupport {
probe.expectMsg(Started)
}
"must undefer underlying when wrapped by widen" in {
"must un-defer underlying when wrapped by widen" in {
val probe = TestProbe[Event]("evt")
val behv = Actor.deferred[Command] { _
probe.ref ! Started
@ -108,7 +106,7 @@ class DeferredSpec extends TypedSpec with StartSupport {
probe.expectMsg(Pong)
}
"must undefer underlying when wrapped by monitor" in {
"must un-defer underlying when wrapped by monitor" in {
// monitor is implemented with tap, so this is testing both
val probe = TestProbe[Event]("evt")
val monitorProbe = TestProbe[Command]("monitor")
@ -132,22 +130,22 @@ class DeferredStubbedSpec extends TypedSpec {
import DeferredSpec._
def mkCtx(behv: Behavior[Command]): EffectfulActorContext[Command] =
new EffectfulActorContext("ctx", behv, 1000, system)
def mkCtx(behv: Behavior[Command]): BehaviorTestkit[Command] =
BehaviorTestkit(behv, "ctx")
"must create underlying deferred behavior immediately" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val behv = Actor.deferred[Command] { _
inbox.ref ! Started
target(inbox.ref)
}
val ctx = mkCtx(behv)
mkCtx(behv)
// it's supposed to be created immediately (not waiting for first message)
inbox.receiveMsg() should ===(Started)
}
"must stop when exception from factory" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val exc = new RuntimeException("simulated exc from factory") with NoStackTrace
val behv = Actor.deferred[Command] { _
inbox.ref ! Started

View file

@ -5,10 +5,10 @@ package akka.actor.typed
import scala.concurrent.duration._
import akka.actor.typed.scaladsl.Actor._
import akka.typed.testkit.{ EffectfulActorContext, Inbox, TestKitSettings }
import akka.testkit.typed.{ BehaviorTestkit, TestInbox, TestKitSettings }
import scala.util.control.NoStackTrace
import akka.typed.testkit.scaladsl._
import akka.testkit.typed.scaladsl._
object RestarterSpec {
@ -68,12 +68,12 @@ class RestarterSpec extends TypedSpec {
import RestarterSpec._
def mkCtx(behv: Behavior[Command]): EffectfulActorContext[Command] =
new EffectfulActorContext("ctx", behv, 1000, system)
def mkCtx(behv: Behavior[Command]): BehaviorTestkit[Command] =
BehaviorTestkit(behv, "ctx")
"A restarter" must {
"receive message" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val behv = supervise(target(inbox.ref)).onFailure[Throwable](SupervisorStrategy.restart)
val ctx = mkCtx(behv)
ctx.run(Ping)
@ -81,7 +81,7 @@ class RestarterSpec extends TypedSpec {
}
"stop when no supervise" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val behv = target(inbox.ref)
val ctx = mkCtx(behv)
intercept[Exc3] {
@ -91,7 +91,7 @@ class RestarterSpec extends TypedSpec {
}
"stop when unhandled exception" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val behv = supervise(target(inbox.ref)).onFailure[Exc1](SupervisorStrategy.restart)
val ctx = mkCtx(behv)
intercept[Exc3] {
@ -101,7 +101,7 @@ class RestarterSpec extends TypedSpec {
}
"restart when handled exception" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val behv = supervise(target(inbox.ref)).onFailure[Exc1](SupervisorStrategy.restart)
val ctx = mkCtx(behv)
ctx.run(NextState)
@ -115,7 +115,7 @@ class RestarterSpec extends TypedSpec {
}
"resume when handled exception" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val behv = supervise(target(inbox.ref)).onFailure[Exc1](SupervisorStrategy.resume)
val ctx = mkCtx(behv)
ctx.run(NextState)
@ -128,7 +128,7 @@ class RestarterSpec extends TypedSpec {
}
"support nesting to handle different exceptions" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val behv =
supervise(
supervise(
@ -159,7 +159,7 @@ class RestarterSpec extends TypedSpec {
}
"not catch fatal error" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val behv = supervise(target(inbox.ref)).onFailure[Throwable](SupervisorStrategy.restart)
val ctx = mkCtx(behv)
intercept[StackOverflowError] {
@ -169,7 +169,7 @@ class RestarterSpec extends TypedSpec {
}
"stop after restart retries limit" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val strategy = SupervisorStrategy.restartWithLimit(maxNrOfRetries = 2, withinTimeRange = 1.minute)
val behv = supervise(target(inbox.ref)).onFailure[Exc1](strategy)
val ctx = mkCtx(behv)
@ -184,7 +184,7 @@ class RestarterSpec extends TypedSpec {
}
"reset retry limit after withinTimeRange" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val withinTimeRange = 2.seconds
val strategy = SupervisorStrategy.restartWithLimit(maxNrOfRetries = 2, withinTimeRange)
val behv = supervise(target(inbox.ref)).onFailure[Exc1](strategy)
@ -206,7 +206,7 @@ class RestarterSpec extends TypedSpec {
}
"stop at first exception when restart retries limit is 0" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val strategy = SupervisorStrategy.restartWithLimit(maxNrOfRetries = 0, withinTimeRange = 1.minute)
val behv = supervise(target(inbox.ref)).onFailure[Exc1](strategy)
val ctx = mkCtx(behv)
@ -217,12 +217,12 @@ class RestarterSpec extends TypedSpec {
}
"create underlying deferred behavior immediately" in {
val inbox = Inbox[Event]("evt")
val inbox = TestInbox[Event]("evt")
val behv = supervise(deferred[Command] { _
inbox.ref ! Started
target(inbox.ref)
}).onFailure[Exc1](SupervisorStrategy.restart)
val ctx = mkCtx(behv)
mkCtx(behv)
// it's supposed to be created immediately (not waiting for first message)
inbox.receiveMsg() should ===(Started)
}

View file

@ -12,8 +12,8 @@ import scala.util.control.NoStackTrace
import akka.actor.typed.scaladsl.Actor
import akka.actor.typed.scaladsl.TimerScheduler
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl._
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl._
class TimerSpec extends TypedSpec(
"""

View file

@ -22,13 +22,12 @@ import akka.actor.typed.scaladsl.Actor._
import org.scalatest.concurrent.ScalaFutures
import org.scalactic.TypeCheckedTripleEquals
import org.scalactic.CanEqual
import org.junit.runner.RunWith
import scala.util.control.NonFatal
import akka.actor.typed.scaladsl.AskPattern
import scala.util.control.NoStackTrace
import akka.typed.testkit.{ Inbox, TestKitSettings }
import akka.testkit.typed.{ TestInbox, TestKitSettings }
import org.scalatest.time.Span
/**
@ -52,7 +51,7 @@ trait StartSupport {
def start[T](behv: Behavior[T]): ActorRef[T] = {
import akka.actor.typed.scaladsl.AskPattern._
import akka.typed.testkit.scaladsl._
import akka.testkit.typed.scaladsl._
implicit val testSettings = TestKitSettings(system)
Await.result(system ? TypedSpec.Create(behv, nextName()), 3.seconds.dilated)
}
@ -145,7 +144,7 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup {
/**
* Group assertion that ensures that the given inboxes are empty.
*/
def assertEmpty(inboxes: Inbox[_]*): Unit = {
def assertEmpty(inboxes: TestInbox[_]*): Unit = {
inboxes foreach (i withClue(s"inbox $i had messages")(i.hasMessages should be(false)))
}
@ -163,8 +162,6 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup {
object TypedSpec {
import akka.{ typed t }
sealed abstract class Start
case object Start extends Start

View file

@ -6,9 +6,7 @@ package internal
import akka.actor.InvalidMessageException
import akka.actor.typed.scaladsl.Actor
import akka.actor.typed.scaladsl.Actor._
import akka.typed.testkit.Inbox
import org.scalactic.ConversionCheckedTripleEquals
import akka.testkit.typed.TestInbox
import org.scalatest._
import org.scalatest.concurrent.{ Eventually, ScalaFutures }
@ -16,7 +14,8 @@ import scala.concurrent.duration._
import scala.concurrent.{ Future, Promise }
import scala.util.control.NonFatal
class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll with ScalaFutures with Eventually with ConversionCheckedTripleEquals {
class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll
with ScalaFutures with Eventually {
override implicit val patienceConfig = PatienceConfig(1.second)
def system[T](behavior: Behavior[T], name: String) = ActorSystem(behavior, name)
@ -37,46 +36,56 @@ class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll with
}
"An ActorSystem" must {
"must start the guardian actor and terminate when it terminates" in {
val t = withSystem("a", immutable[Probe] { case (_, p) p.replyTo ! p.msg; stopped }, doTerminate = false) { sys
val inbox = Inbox[String]("a")
sys ! Probe("hello", inbox.ref)
eventually {
inbox.hasMessages should ===(true)
"start the guardian actor and terminate when it terminates" in {
val t = withSystem(
"a",
Actor.immutable[Probe] { case (_, p) p.replyTo ! p.msg; Actor.stopped }, doTerminate = false) { sys
val inbox = TestInbox[String]("a")
sys ! Probe("hello", inbox.ref)
eventually {
inbox.hasMessages should ===(true)
}
inbox.receiveAll() should ===("hello" :: Nil)
}
inbox.receiveAll() should ===("hello" :: Nil)
}
val p = t.ref.path
p.name should ===("/")
p.address.system should ===(suite + "-a")
}
"must terminate the guardian actor" in {
val inbox = Inbox[String]("terminate")
// see issue #24172
"shutdown if guardian shuts down immediately" in {
pending
withSystem("shutdown", Actor.stopped[String], doTerminate = false) { sys: ActorSystem[String]
sys.whenTerminated.futureValue
}
}
"terminate the guardian actor" in {
val inbox = TestInbox[String]("terminate")
val sys = system(
immutable[Probe] {
case (_, _) unhandled
Actor.immutable[Probe] {
case (_, _) Actor.unhandled
} onSignal {
case (ctx, PostStop)
case (_, PostStop)
inbox.ref ! "done"
same
Actor.same
},
"terminate")
sys.terminate().futureValue
inbox.receiveAll() should ===("done" :: Nil)
}
"must log to the event stream" in {
"log to the event stream" in {
pending
}
"must have a name" in {
"have a name" in {
withSystem("name", Actor.empty[String]) { sys
sys.name should ===(suite + "-name")
}
}
"must report its uptime" in {
"report its uptime" in {
withSystem("uptime", Actor.empty[String]) { sys
sys.uptime should be < 1L
Thread.sleep(1000)
@ -84,7 +93,7 @@ class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll with
}
}
"must have a working thread factory" in {
"have a working thread factory" in {
withSystem("thread", Actor.empty[String]) { sys
val p = Promise[Int]
sys.threadFactory.newThread(new Runnable {
@ -94,14 +103,14 @@ class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll with
}
}
"must be able to run Futures" in {
"be able to run Futures" in {
withSystem("futures", Actor.empty[String]) { sys
val f = Future(42)(sys.executionContext)
f.futureValue should ===(42)
}
}
"must not allow null messages" in {
"not allow null messages" in {
withSystem("null-messages", Actor.empty[String]) { sys
intercept[InvalidMessageException] {
sys ! null

View file

@ -7,10 +7,10 @@ import akka.actor.typed._
import akka.actor.typed.receptionist.Receptionist._
import akka.actor.typed.scaladsl.Actor
import akka.actor.typed.scaladsl.AskPattern._
import akka.typed.testkit.EffectfulActorContext
import akka.typed.testkit.Inbox
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl.TestProbe
import akka.testkit.typed.BehaviorTestkit
import akka.testkit.typed.TestInbox
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl.TestProbe
import org.scalatest.concurrent.Eventually
import scala.concurrent.duration._
@ -26,7 +26,7 @@ class LocalReceptionistSpec extends TypedSpec with Eventually with StartSupport
val behaviorB = Actor.empty[ServiceB]
case object Stop extends ServiceA with ServiceB
val stoppableBehavior = Actor.immutable[Any] { (ctx, msg)
val stoppableBehavior = Actor.immutable[Any] { (_, msg)
msg match {
case Stop Behavior.stopped
case _ Behavior.same
@ -44,29 +44,29 @@ class LocalReceptionistSpec extends TypedSpec with Eventually with StartSupport
"A local receptionist" must {
"must register a service" in {
val ctx = new EffectfulActorContext("register", behavior, 1000, system)
val a = Inbox[ServiceA]("a")
val r = Inbox[Registered[_]]("r")
val ctx = new BehaviorTestkit("register", behavior)
val a = TestInbox[ServiceA]("a")
val r = TestInbox[Registered[_]]("r")
ctx.run(Register(ServiceKeyA, a.ref)(r.ref))
ctx.getEffect() // watching however that is implemented
ctx.retrieveEffect() // watching however that is implemented
r.receiveMsg() should be(Registered(ServiceKeyA, a.ref))
val q = Inbox[Listing[ServiceA]]("q")
val q = TestInbox[Listing[ServiceA]]("q")
ctx.run(Find(ServiceKeyA)(q.ref))
ctx.getAllEffects() should be(Nil)
ctx.retrieveAllEffects() should be(Nil)
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref)))
assertEmpty(a, r, q)
}
"must register two services" in {
val ctx = new EffectfulActorContext("registertwo", behavior, 1000, system)
val a = Inbox[ServiceA]("a")
val r = Inbox[Registered[_]]("r")
val ctx = new BehaviorTestkit("registertwo", behavior)
val a = TestInbox[ServiceA]("a")
val r = TestInbox[Registered[_]]("r")
ctx.run(Register(ServiceKeyA, a.ref)(r.ref))
r.receiveMsg() should be(Registered(ServiceKeyA, a.ref))
val b = Inbox[ServiceB]("b")
val b = TestInbox[ServiceB]("b")
ctx.run(Register(ServiceKeyB, b.ref)(r.ref))
r.receiveMsg() should be(Registered(ServiceKeyB, b.ref))
val q = Inbox[Listing[_]]("q")
val q = TestInbox[Listing[_]]("q")
ctx.run(Find(ServiceKeyA)(q.ref))
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref)))
ctx.run(Find(ServiceKeyB)(q.ref))
@ -75,15 +75,15 @@ class LocalReceptionistSpec extends TypedSpec with Eventually with StartSupport
}
"must register two services with the same key" in {
val ctx = new EffectfulActorContext("registertwosame", behavior, 1000, system)
val a1 = Inbox[ServiceA]("a1")
val r = Inbox[Registered[_]]("r")
val ctx = new BehaviorTestkit("registertwosame", behavior)
val a1 = TestInbox[ServiceA]("a1")
val r = TestInbox[Registered[_]]("r")
ctx.run(Register(ServiceKeyA, a1.ref)(r.ref))
r.receiveMsg() should be(Registered(ServiceKeyA, a1.ref))
val a2 = Inbox[ServiceA]("a2")
val a2 = TestInbox[ServiceA]("a2")
ctx.run(Register(ServiceKeyA, a2.ref)(r.ref))
r.receiveMsg() should be(Registered(ServiceKeyA, a2.ref))
val q = Inbox[Listing[_]]("q")
val q = TestInbox[Listing[_]]("q")
ctx.run(Find(ServiceKeyA)(q.ref))
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a1.ref, a2.ref)))
ctx.run(Find(ServiceKeyB)(q.ref))

View file

@ -4,8 +4,8 @@
package akka.actor.typed
package scaladsl
import akka.typed.testkit.{ EffectfulActorContext, TestKitSettings }
import akka.typed.testkit.scaladsl.TestProbe
import akka.testkit.typed.{ BehaviorTestkit, TestKitSettings }
import akka.testkit.typed.scaladsl.TestProbe
import scala.concurrent.duration.DurationInt
class ImmutablePartialSpec extends TypedSpec with StartSupport {
@ -22,7 +22,7 @@ class ImmutablePartialSpec extends TypedSpec with StartSupport {
probe.ref ! Command2
Actor.same
}
val context = new EffectfulActorContext("ctx", behavior, 42, null)
val context = new BehaviorTestkit("ctx", behavior)
context.run(Command1)
context.currentBehavior shouldBe behavior

View file

@ -5,8 +5,8 @@ package akka.actor.typed
package scaladsl
import akka.Done
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl.TestProbe
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl.TestProbe
final class OnSignalSpec extends TypedSpec with StartSupport {

View file

@ -0,0 +1,45 @@
package docs.akka.typed.testing.async
import akka.actor.typed.scaladsl.Actor
import akka.actor.typed._
import akka.testkit.typed.TestKit
import akka.testkit.typed.scaladsl._
import org.scalatest._
object BasicAsyncTestingSpec {
//#under-test
case class Ping(msg: String, response: ActorRef[Pong])
case class Pong(msg: String)
val echoActor = Actor.immutable[Ping] { (_, msg)
msg match {
case Ping(m, replyTo)
replyTo ! Pong(m)
Actor.same
}
}
//#under-test
}
//#test-header
class BasicAsyncTestingSpec extends TestKit(ActorSystem(Actor.empty, "BasicTestingSpec"))
with WordSpecLike with BeforeAndAfterAll {
//#test-header
import BasicAsyncTestingSpec._
"A testkit" must {
"support verifying a response" in {
//#test
val probe = TestProbe[Pong]()
val pinger = actorOf(echoActor, "ping")
pinger ! Ping("hello", probe.ref)
probe.expectMsg(Pong("hello"))
//#test
}
}
//#test-shutdown
override def afterAll(): Unit = shutdown()
//#test-shutdown
}

View file

@ -0,0 +1,99 @@
package docs.akka.typed.testing.sync
//#imports
import akka.actor.typed._
import akka.actor.typed.scaladsl._
import akka.testkit.typed._
import akka.testkit.typed.Effect._
//#imports
import org.scalatest.{ Matchers, WordSpec }
object BasicSyncTestingSpec {
//#child
val childActor = Actor.immutable[String] { (_, _)
Actor.same[String]
}
//#child
//#under-test
sealed 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
val myBehaviour = Actor.immutablePartial[Cmd] {
case (ctx, CreateChild(name))
ctx.spawn(childActor, name)
Actor.same
case (ctx, CreateAnonymousChild)
ctx.spawnAnonymous(childActor)
Actor.same
case (ctx, SayHelloToChild(childName))
val child: ActorRef[String] = ctx.spawn(childActor, childName)
child ! "hello"
Actor.same
case (ctx, SayHelloToAnonymousChild)
val child: ActorRef[String] = ctx.spawnAnonymous(childActor)
child ! "hello stranger"
Actor.same
case (_, SayHello(who))
who ! "hello"
Actor.same
//#under-test
}
}
class BasicSyncTestingSpec extends WordSpec with Matchers {
import BasicSyncTestingSpec._
"Typed actor synchronous testing" must {
"record spawning" in {
//#test-child
val testKit = BehaviorTestkit(myBehaviour)
testKit.run(CreateChild("child"))
testKit.expectEffect(Spawned(childActor, "child"))
//#test-child
}
"record spawning anonymous" in {
//#test-anonymous-child
val testKit = BehaviorTestkit(myBehaviour)
testKit.run(CreateAnonymousChild)
testKit.expectEffect(SpawnedAnonymous(childActor))
//#test-anonymous-child
}
"record message sends" in {
//#test-message
val testKit = BehaviorTestkit(myBehaviour)
val inbox = TestInbox[String]()
testKit.run(SayHello(inbox.ref))
inbox.expectMsg("hello")
//#test-message
}
"send a message to a spawned child" in {
//#test-child-message
val testKit = BehaviorTestkit(myBehaviour)
testKit.run(SayHelloToChild("child"))
val childInbox = testKit.childInbox[String]("child")
childInbox.expectMsg("hello")
//#test-child-message
}
"send a message to an anonymous spawned child" in {
//#test-child-message-anonymous
val testKit = BehaviorTestkit(myBehaviour)
testKit.run(SayHelloToAnonymousChild)
// Anonymous actors are created as: $a $b etc
val childInbox = testKit.childInbox[String]("$a")
childInbox.expectMsg("hello stranger")
//#test-child-message-anonymous
}
}
}

View file

@ -10,7 +10,7 @@ import java.lang.annotation.*;
* Marks APIs that are meant to evolve towards becoming stable APIs, but are not stable APIs yet.
*
* Evolving interfaces MAY change from one patch release to another (i.e. 2.4.10 to 2.4.11) without up-front notice.
* A best-effort approach is taken to not cause more breakage than really neccessary, and usual deprecation techniques
* A best-effort approach is taken to not cause more breakage than really necessary, and usual deprecation techniques
* are utilised while evolving these APIs, however there is NO strong guarantee regarding the source or binary
* compatibility of APIs marked using this annotation.
*

View file

@ -10,8 +10,8 @@ import org.scalatest.concurrent.ScalaFutures
import scala.concurrent.duration._
import akka.actor.typed.Behavior
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl.TestProbe
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl.TestProbe
import akka.persistence.typed.scaladsl.PersistentActor
import akka.persistence.typed.scaladsl.PersistentActor.PersistNothing

View file

@ -10,8 +10,8 @@ import akka.cluster.typed.Cluster
import akka.actor.typed.internal.adapter.ActorSystemAdapter
import akka.actor.typed.scaladsl.Actor
import akka.actor.typed.scaladsl.adapter._
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl.TestProbe
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl.TestProbe
import com.typesafe.config.ConfigFactory
import org.scalatest.concurrent.ScalaFutures

View file

@ -2,8 +2,8 @@ package akka.cluster.typed;
import akka.cluster.ClusterEvent;
import akka.actor.typed.ActorSystem;
import akka.typed.testkit.TestKitSettings;
import akka.typed.testkit.javadsl.TestProbe;
import akka.testkit.typed.TestKitSettings;
import akka.testkit.typed.javadsl.TestProbe;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.junit.Test;

View file

@ -8,8 +8,8 @@ import akka.actor.typed.{ ActorRef, ActorSystem, Behavior, StartSupport, TypedSp
import akka.actor.typed.scaladsl.Actor
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.scaladsl.adapter._
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl._
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl._
import akka.cluster.Cluster
import akka.cluster.ddata.{ GCounter, GCounterKey, ReplicatedData }
import akka.cluster.ddata.typed.scaladsl.Replicator._

View file

@ -8,8 +8,8 @@ import akka.cluster.MemberStatus
import akka.actor.typed.TypedSpec
import akka.actor.typed.internal.adapter.ActorSystemAdapter
import akka.actor.typed.scaladsl.adapter._
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl.TestProbe
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl.TestProbe
import com.typesafe.config.ConfigFactory
import org.scalatest.concurrent.ScalaFutures

View file

@ -9,8 +9,8 @@ import akka.actor.ExtendedActorSystem
import akka.serialization.SerializerWithStringManifest
import akka.actor.typed.scaladsl.Actor
import akka.actor.typed.scaladsl.adapter._
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl.TestProbe
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl.TestProbe
import akka.actor.typed.{ ActorRef, ActorRefResolver, Props, TypedSpec }
import com.typesafe.config.ConfigFactory
import org.scalatest.concurrent.ScalaFutures

View file

@ -10,8 +10,8 @@ import akka.actor.typed.Props
import akka.actor.typed.TypedSpec
import akka.persistence.typed.scaladsl.PersistentActor
import akka.persistence.typed.scaladsl.PersistentActor.{ CommandHandler, Effect }
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl.TestProbe
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl.TestProbe
import com.typesafe.config.ConfigFactory
import org.scalatest.concurrent.ScalaFutures

View file

@ -13,8 +13,8 @@ import akka.actor.typed.internal.adapter.ActorSystemAdapter
import akka.actor.typed.receptionist.Receptionist
import akka.actor.typed.scaladsl.Actor
import akka.actor.typed.scaladsl.adapter._
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl.TestProbe
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl.TestProbe
import com.typesafe.config.ConfigFactory
import scala.concurrent.Await

View file

@ -9,7 +9,13 @@ This module is currently marked as @ref:[may change](common/may-change.md) in th
@@@
## Dependency
### Migrating to 2.5.9
* `EffectfulActorContext` has been renamed to `BehaviourTestKit`
* `Inbox` has been renamed to `TestInbox` to allign with `TestProbe`
* Separated into modules e.g. `akka-actor-typed` `akka-persistence-typed` along with matching package names
### Dependency
To use typed actors add the following dependency:

View file

@ -2,7 +2,7 @@
TODO
## Dependency
### Dependency
@@dependency [sbt,Maven,Gradle] {
group=com.typesafe.akka

View file

@ -1,9 +1,5 @@
# Cluster
TODO
## Dependency
sbt
: @@@vars
```
@ -29,4 +25,6 @@ Maven
<version>$akka.version$</version>
</dependency>
```
@@@
@@@
TODO

View file

@ -11,33 +11,165 @@ This module is currently marked as @ref:[may change](common/may-change.md) in th
@@@
To use the testkit add the following dependency:
## Dependency
To use the testkit add the following dependency:
sbt
: @@@vars
```
"com.typesafe.akka" %% "akka-testkit-typed" % "$akka.version$"
```
@@@
@@dependency [sbt,Maven,Gradle] {
group=com.typesafe.akka
artifact=akka-testkit-typed_2.11
version=$version$
scope=test
}
Testing can either be done asynchronously using a real `ActorSystem` or synchronously on the testing thread using the `BehaviousTestKit`.
Gradle
: @@@vars
```
dependencies {
compile group: 'com.typesafe.akka', name: 'akka-testkit-typed_2.11', version: '$akka.version$'
}
```
@@@
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.
Maven
: @@@vars
```
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-testkit-typed_$scala.binary_version$</artifactId>
<version>$akka.version$</version>
</dependency>
```
@@@
Certain `Behavior`s 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.
TODO
## Synchronous behaviour 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
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #imports }
Java
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #imports }
Each of the tests are testing an actor that based on the message executes a different effect to be tested:
Scala
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #under-test }
Java
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #under-test }
For creating a child actor a noop actor is created:
Scala
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #child }
Java
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #child }
All of the tests make use of the `BehaviourTestkit` 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
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #test-child }
Java
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #test-child }
Anonymously:
Scala
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #test-anonymous-child }
Java
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #test-anonymous-child }
### 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
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #test-message }
Java
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #test-message }
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
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #test-child-message }
Java
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #test-child-message }
For anonymous children the actor names are generated in a deterministic way:
Scala
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #test-child-message-anonymous }
Java
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #test-child-message-anonymous }
### Testing other effects
The `BehaviorTestkit` keeps track other effects you can verify, look at the sub-classes of `akka.testkit.typed.Effect`
* SpawnedAdapter
* Stopped
* Watched
* Unwatched
* Scheduled
See the other public methods and API documentation on `BehaviourTestkit` for other types of verification.
## Asynchronous testing
Asynchronous testing uses a real `ActorSystem` that allows you to test your Actors in a 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
: @@snip [BasicAsyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/async/BasicAsyncTestingSpec.scala) { #under-test }
Java
: @@snip [BasicAsyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/async/BasicAsyncTestingTest.java) { #under-test }
Tests can optionally extend `TestKit` or include the `TestKitBase`.
Scala
: @@snip [BasicTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/async/BasicAsyncTestingSpec.scala) { #test-header }
Java
: @@snip [BasicAsyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/async/BasicAsyncTestingTest.java) { #test-header }
Your test is responsible for shutting down the `ActorSystem` e.g. using `BeforeAndAfterAll` when using ScalaTest
Scala
: @@snip [BasicTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/async/BasicAsyncTestingSpec.scala) { #test-shutdown }
Java
: @@snip [BasicAsyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/async/BasicAsyncTestingTest.java) { #test-shutdown }
The following demonstrates:
* Creating a typed actor from the `TestKit`'s system using `actorOf`
* Creating a typed `TestProbe`
* Verifying that the actor under test responds via the `TestProbe`
Scala
: @@snip [BasicTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/async/BasicAsyncTestingSpec.scala) { #test }
Java
: @@snip [BasicAsyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/async/BasicAsyncTestingTest.java) { #test }

View file

@ -6,13 +6,10 @@ package akka.persistence.typed.scaladsl
import scala.concurrent.duration._
import akka.actor.typed.{ ActorRef, ActorSystem, Behavior, StartSupport, SupervisorStrategy, Terminated, TypedSpec }
import akka.actor.typed.scaladsl.Actor
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.scaladsl.adapter._
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl._
import akka.testkit.typed.TestKitSettings
import akka.testkit.typed.scaladsl._
import com.typesafe.config.ConfigFactory
import org.scalatest.concurrent.Eventually
import akka.util.Timeout
import akka.persistence.typed.scaladsl.PersistentActor._
object PersistentActorSpec {

View file

@ -4,18 +4,21 @@
package akka.actor.typed
package internal
import akka.{ actor a, event e }
import scala.concurrent._
import com.typesafe.config.ConfigFactory
import java.util.concurrent.ThreadFactory
import akka.annotation.InternalApi
import akka.event.Logging
import akka.event.typed.{ BusLogging, DefaultLoggingFilter, EventStream }
import akka.event.typed.{ BusLogging, DefaultLoggingFilter }
import akka.util.Timeout
import akka.{ actor a, event e }
import com.typesafe.config.ConfigFactory
private[typed] class ActorSystemStub(val name: String)
import scala.concurrent._
/**
* INTERNAL API
*/
@InternalApi private[akka] class ActorSystemStub(val name: String)
extends ActorSystem[Nothing] with ActorRef[Nothing] with ActorRefImpl[Nothing] {
override val path: a.ActorPath = a.RootActorPath(a.Address("akka", name)) / "user"

View file

@ -3,9 +3,10 @@
*/
package akka.actor.typed.internal
import scala.concurrent.ExecutionContextExecutor
import java.util.LinkedList
import scala.concurrent.ExecutionContextExecutor
class ControlledExecutor extends ExecutionContextExecutor {
private val tasks = new LinkedList[Runnable]

View file

@ -4,11 +4,17 @@
package akka.actor.typed
package internal
import akka.{ actor a }
import java.util.concurrent.ConcurrentLinkedQueue
import akka.annotation.InternalApi
import akka.{ actor a }
import scala.annotation.tailrec
private[typed] class DebugRef[T](override val path: a.ActorPath, override val isLocal: Boolean)
/**
* INTERNAL API
*/
@InternalApi private[akka] class DebugRef[T](override val path: a.ActorPath, override val isLocal: Boolean)
extends ActorRef[T] with ActorRefImpl[T] {
private val q = new ConcurrentLinkedQueue[Either[SystemMessage, T]]

View file

@ -0,0 +1,206 @@
/**
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.testkit.typed
import java.util.concurrent.ConcurrentLinkedQueue
import akka.actor.typed.{ ActorRef, Behavior, PostStop, Props, Signal }
import akka.annotation.{ ApiMayChange, InternalApi }
import scala.annotation.tailrec
import scala.collection.immutable
import scala.util.control.Exception.Catcher
import scala.util.control.NonFatal
import scala.concurrent.duration.{ Duration, FiniteDuration }
import scala.language.existentials
/**
* All tracked effects must extend implement this type. It is deliberately
* not sealed in order to allow extensions.
*/
abstract class Effect
// TODO offer a better Java API for default params that are rarely used e.g. props
@ApiMayChange
object Effect {
abstract class SpawnedEffect extends Effect
@SerialVersionUID(1L) final case class Spawned(behavior: Behavior[_], childName: String, props: Props = Props.empty) extends SpawnedEffect
@SerialVersionUID(1L) final case class SpawnedAnonymous(behaviour: Behavior[_], props: Props = Props.empty) extends SpawnedEffect
@SerialVersionUID(1L) final case object SpawnedAdapter extends SpawnedEffect
@SerialVersionUID(1L) final case class Stopped(childName: String) extends Effect
@SerialVersionUID(1L) final case class Watched[T](other: ActorRef[T]) extends Effect
@SerialVersionUID(1L) final case class Unwatched[T](other: ActorRef[T]) extends Effect
@SerialVersionUID(1L) final case class ReceiveTimeoutSet[T](d: Duration, msg: T) extends Effect
@SerialVersionUID(1L) final case class Scheduled[U](delay: FiniteDuration, target: ActorRef[U], msg: U) extends Effect
@SerialVersionUID(1L) case object NoEffects extends Effect
}
/**
* INTERNAL API
*/
@InternalApi private[akka] class EffectfulActorContext[T](name: String) extends StubbedActorContext[T](name) {
import Effect._
import akka.{ actor a }
/**
* INTERNAL API
*/
@InternalApi private[akka] val effectQueue = new ConcurrentLinkedQueue[Effect]
override def spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty): ActorRef[U] = {
val ref = super.spawnAnonymous(behavior, props)
effectQueue.offer(SpawnedAnonymous(behavior, props))
ref
}
override def spawnAdapter[U](f: U T): ActorRef[U] = {
spawnAdapter(f, "")
}
override def spawnAdapter[U](f: U T, name: String): ActorRef[U] = {
val ref = super.spawnAdapter(f, name)
effectQueue.offer(SpawnedAdapter)
ref
}
override def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty): ActorRef[U] = {
effectQueue.offer(Spawned(behavior, name, props))
super.spawn(behavior, name, props)
}
override def stop[U](child: ActorRef[U]): Boolean = {
effectQueue.offer(Stopped(child.path.name))
super.stop(child)
}
override def watch[U](other: ActorRef[U]): Unit = {
effectQueue.offer(Watched(other))
super.watch(other)
}
override def unwatch[U](other: ActorRef[U]): Unit = {
effectQueue.offer(Unwatched(other))
super.unwatch(other)
}
override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = {
effectQueue.offer(ReceiveTimeoutSet(d, msg))
super.setReceiveTimeout(d, msg)
}
override def cancelReceiveTimeout(): Unit = {
effectQueue.offer(ReceiveTimeoutSet(Duration.Undefined, null))
super.cancelReceiveTimeout()
}
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): a.Cancellable = {
effectQueue.offer(Scheduled(delay, target, msg))
super.schedule(delay, target, msg)
}
}
@ApiMayChange
object BehaviorTestkit {
def apply[T](initialBehavior: Behavior[T], name: String): BehaviorTestkit[T] =
new BehaviorTestkit[T](name, initialBehavior)
def apply[T](initialBehavior: Behavior[T]): BehaviorTestkit[T] =
apply(initialBehavior, "testkit")
/**
* JAVA API
*/
def create[T](initialBehavior: Behavior[T], name: String): BehaviorTestkit[T] =
new BehaviorTestkit[T](name, initialBehavior)
/**
* JAVA API
*/
def create[T](initialBehavior: Behavior[T]): BehaviorTestkit[T] =
apply(initialBehavior, "ctx")
}
/**
* Used for testing [[Behavior]]s. Stores all effects e.g. Spawning of children,
* watching and offers access to what effects have taken place.
*/
@ApiMayChange
class BehaviorTestkit[T](_name: String, _initialBehavior: Behavior[T]) {
import Effect._
// really this should be private, make so when we port out tests that need it
private[akka] val ctx = new EffectfulActorContext[T](_name)
/**
* Requests the oldest [[Effect]] or [[NoEffects]] if no effects
* have taken place. The effect is consumed, subsequent calls won't
* will not include this effect.
*/
def retrieveEffect(): Effect = ctx.effectQueue.poll() match {
case null NoEffects
case x x
}
def childInbox[U](name: String): TestInbox[U] = {
val inbox = ctx.childInbox[U](name)
assert(inbox.isDefined, s"Child not created: $name. Children created: [${ctx.childrenNames.mkString(",")}]")
inbox.get
}
def selfInbox(): TestInbox[T] = ctx.selfInbox
/**
* Requests all the effects. The effects are consumed, subsequent calls will only
* see new effects.
*/
def retrieveAllEffects(): immutable.Seq[Effect] = {
@tailrec def rec(acc: List[Effect]): List[Effect] = ctx.effectQueue.poll() match {
case null acc.reverse
case x rec(x :: acc)
}
rec(Nil)
}
/**
* Asserts that the oldest effect is the expectedEffect. Removing it from
* further assertions.
*/
def expectEffect(expectedEffect: Effect): Unit = {
ctx.effectQueue.poll() match {
case null assert(assertion = false, s"expected: $expectedEffect but no effects were recorded")
case effect assert(expectedEffect == effect, s"expected: $expectedEffect but for $effect")
}
}
private var current = Behavior.validateAsInitial(Behavior.undefer(_initialBehavior, ctx))
def currentBehavior: Behavior[T] = current
def isAlive: Boolean = Behavior.isAlive(current)
private def handleException: Catcher[Unit] = {
case NonFatal(e)
try Behavior.canonicalize(Behavior.interpretSignal(current, ctx, PostStop), current, ctx) // TODO why canonicalize here?
catch {
case NonFatal(_) /* ignore, real is logging */
}
throw e
}
/**
* Send the msg to the behavior and record any [[Effect]]s
*/
def run(msg: T): Unit = {
try {
current = Behavior.canonicalize(Behavior.interpretMessage(current, ctx, msg), current, ctx)
} catch handleException
}
/**
* Send the signal to the beheavior and record any [[Effect]]s
*/
def signal(signal: Signal): Unit = {
try {
current = Behavior.canonicalize(Behavior.interpretSignal(current, ctx, signal), current, ctx)
} catch handleException
}
}

View file

@ -1,4 +1,4 @@
package akka.typed.testkit
package akka.testkit.typed
import akka.actor.InvalidMessageException
import akka.{ actor untyped }
@ -11,7 +11,7 @@ import scala.collection.immutable.TreeMap
import scala.concurrent.ExecutionContextExecutor
import scala.concurrent.duration.FiniteDuration
import akka.annotation.InternalApi
import akka.actor.typed.internal.{ ActorContextImpl, ActorRefImpl }
import akka.actor.typed.internal.{ ActorContextImpl, ActorRefImpl, ActorSystemStub }
import scala.annotation.tailrec
import scala.util.control.NonFatal
@ -31,7 +31,7 @@ private[akka] final class FunctionRef[-T](
if (msg == null) throw InvalidMessageException("[null] is not an allowed message")
if (isAlive)
try send(msg, this) catch {
case NonFatal(ex) // nothing we can do here
case NonFatal(_) // nothing we can do here
}
else () // we dont have deadLetters available
}
@ -39,12 +39,12 @@ private[akka] final class FunctionRef[-T](
import internal._
override def sendSystem(signal: SystemMessage): Unit = signal match {
case internal.Create() // nothing to do
case internal.DeathWatchNotification(ref, cause) // were not watching, and were not a parent either
case internal.Terminate() doTerminate()
case internal.Watch(watchee, watcher) if (watchee == this && watcher != this) addWatcher(watcher.sorryForNothing)
case internal.Unwatch(watchee, watcher) if (watchee == this && watcher != this) remWatcher(watcher.sorryForNothing)
case NoMessage // nothing to do
case internal.Create() // nothing to do
case internal.DeathWatchNotification(_, _) // were not watching, and were not a parent either
case internal.Terminate() doTerminate()
case internal.Watch(watchee, watcher) if (watchee == this && watcher != this) addWatcher(watcher.sorryForNothing)
case internal.Unwatch(watchee, watcher) if (watchee == this && watcher != this) remWatcher(watcher.sorryForNothing)
case NoMessage // nothing to do
}
override def isLocal = true
@ -105,27 +105,37 @@ private[typed] object WatchableRef {
}
/**
* INTERNAL API
*
* An [[ActorContext]] for synchronous execution of a [[Behavior]] that
* provides only stubs for the effects an Actor can perform and replaces
* created child Actors by a synchronous Inbox (see `Inbox.sync`).
*
* See [[EffectfulActorContext]] for more advanced uses.
* See [[BehaviorTestkit]] for more advanced uses.
*/
class StubbedActorContext[T](
val name: String,
override val mailboxCapacity: Int,
override val system: ActorSystem[Nothing]) extends ActorContextImpl[T] {
@InternalApi private[akka] class StubbedActorContext[T](
val name: String) extends ActorContextImpl[T] {
/**
* INTERNAL API
*/
@InternalApi private[akka] val selfInbox = TestInbox[T](name)
val selfInbox = Inbox[T](name)
override val self = selfInbox.ref
override val system = new ActorSystemStub("StubbedActorContext")
// Not used for a stubbed actor context
override def mailboxCapacity = 1
private var _children = TreeMap.empty[String, Inbox[_]]
private val childName = Iterator from 1 map (Helpers.base64(_))
private var _children = TreeMap.empty[String, TestInbox[_]]
private val childName = Iterator from 0 map (Helpers.base64(_))
override def children: Iterable[ActorRef[Nothing]] = _children.values map (_.ref)
def childrenNames: Iterable[String] = _children.keys
override def child(name: String): Option[ActorRef[Nothing]] = _children get name map (_.ref)
override def spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty): ActorRef[U] = {
val i = Inbox[U](childName.next())
val i = TestInbox[U](childName.next())
_children += i.ref.path.name i
i.ref
}
@ -134,7 +144,7 @@ class StubbedActorContext[T](
case Some(_) throw untyped.InvalidActorNameException(s"actor name $name is already taken")
case None
// FIXME correct child path for the Inbox ref
val i = Inbox[U](name)
val i = TestInbox[U](name)
_children += name i
i.ref
}
@ -160,6 +170,7 @@ class StubbedActorContext[T](
override def isCancelled = true
}
// TODO allow overriding of this
override def executionContext: ExecutionContextExecutor = system.executionContext
/**
@ -168,7 +179,7 @@ class StubbedActorContext[T](
@InternalApi private[akka] def internalSpawnAdapter[U](f: U T, name: String): ActorRef[U] = {
val n = if (name != "") s"${childName.next()}-$name" else childName.next()
val i = Inbox[U](n)
val i = TestInbox[U](n)
_children += i.ref.path.name i
new FunctionRef[U](
@ -181,8 +192,8 @@ class StubbedActorContext[T](
* Retrieve the inbox representing the given child actor. The passed ActorRef must be one that was returned
* by one of the spawn methods earlier.
*/
def childInbox[U](child: ActorRef[U]): Inbox[U] = {
val inbox = _children(child.path.name).asInstanceOf[Inbox[U]]
def childInbox[U](child: ActorRef[U]): TestInbox[U] = {
val inbox = _children(child.path.name).asInstanceOf[TestInbox[U]]
if (inbox.ref != child) throw new IllegalArgumentException(s"$child is not a child of $this")
inbox
}
@ -190,7 +201,7 @@ class StubbedActorContext[T](
/**
* Retrieve the inbox representing the child actor with the given name.
*/
def childInbox[U](name: String): Inbox[U] = _children(name).asInstanceOf[Inbox[U]]
def childInbox[U](name: String): Option[TestInbox[U]] = _children.get(name).map(_.asInstanceOf[TestInbox[U]])
/**
* Remove the given inbox from the list of children, for example after

View file

@ -1,4 +1,4 @@
package akka.typed.testkit
package akka.testkit.typed
import akka.event.Logging.{ LogEvent, StdOutLogger }
import akka.testkit.{ EventFilter, TestEvent TE }
@ -16,7 +16,7 @@ import akka.event.typed.Logger
*
* <pre><code>
* akka.typed {
* loggers = ["akka.typed.testkit.TestEventListener"]
* loggers = ["akka.testkit.typed.TestEventListener"]
* }
* </code></pre>
*/

View file

@ -1,23 +1,26 @@
/**
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.testkit
package akka.testkit.typed
import java.util.concurrent.{ ConcurrentLinkedQueue, ThreadLocalRandom }
import akka.actor.{ Address, RootActorPath }
import akka.actor.typed.ActorRef
import akka.annotation.ApiMayChange
import scala.annotation.tailrec
import scala.collection.immutable
/**
* Utility for receiving messages outside of an actor. No methods are provided
* for synchronously awaiting a message, this is primarily useful for synchronous
* tests of behaviors that send messages to other actors, where an Inboxs ActorRef
* can conveniently be used as a stub.
* Utility for use as an [[ActorRef]] when synchronously testing [[akka.actor.typed.Behavior]]
* to be used along with [[BehaviorTestkit]].
*
* See [[akka.testkit.typed.scaladsl.TestProbe]] for asynchronous testing.
*/
class Inbox[T](name: String) {
@ApiMayChange
class TestInbox[T](name: String) {
def this() = this("inbox")
private val q = new ConcurrentLinkedQueue[T]
@ -27,22 +30,40 @@ class Inbox[T](name: String) {
new FunctionRef[T](path, (msg, self) q.add(msg), (self) ())
}
/**
* Get and remove the oldest message
*/
def receiveMsg(): T = q.poll() match {
case null throw new NoSuchElementException(s"polling on an empty inbox: $name")
case x x
}
/**
* Assert and remove the the oldest message.
*/
def expectMsg(expectedMessage: T): TestInbox[T] = {
q.poll() match {
case null assert(assertion = false, s"expected msg: $expectedMessage but no messages were received")
case message assert(message == expectedMessage, s"expected: $expectedMessage but received $message")
}
this
}
def receiveAll(): immutable.Seq[T] = {
@tailrec def rec(acc: List[T]): List[T] = q.poll() match {
case null acc.reverse
case x rec(x :: acc)
}
rec(Nil)
}
def hasMessages: Boolean = q.peek() != null
// TODO expectNoMsg etc
}
object Inbox {
def apply[T](name: String): Inbox[T] = new Inbox(name)
@ApiMayChange
object TestInbox {
def apply[T](name: String = "inbox"): TestInbox[T] = new TestInbox(name)
}

View file

@ -0,0 +1,51 @@
package akka.testkit.typed
import akka.actor.typed.{ ActorRef, ActorSystem, Behavior }
import akka.annotation.ApiMayChange
import akka.util.Timeout
import scala.concurrent.duration._
import scala.concurrent.{ Await, TimeoutException }
/**
* Testkit for typed actors. Extending this removes some boiler plate when testing
* typed actors.
*
* If a test can't extend then use the [[TestKitBase]] trait
*
* @param _system The [ActorSystem] for the test
*/
@ApiMayChange
class TestKit(_system: ActorSystem[_]) extends TestKitBase {
implicit val system = _system
}
@ApiMayChange
trait TestKitBase {
def system: ActorSystem[_]
implicit def testkitSettings = TestKitSettings(system)
def shutdown(): Unit = {
shutdown(system, 5.seconds)
}
def shutdown(
actorSystem: ActorSystem[_],
duration: Duration,
verifySystemShutdown: Boolean = false): Unit = {
system.terminate()
try Await.ready(actorSystem.whenTerminated, duration) catch {
case _: TimeoutException
val msg = "Failed to stop [%s] within [%s] \n%s".format(actorSystem.name, duration,
actorSystem.printTree)
if (verifySystemShutdown) throw new RuntimeException(msg)
else println(msg)
}
}
// The only current impl of a typed actor system returns a Future.successful currently
// hence the hardcoded timeouts
def actorOf[T](behaviour: Behavior[T], name: String): ActorRef[T] =
Await.result(system.systemActorOf(behaviour, name)(Timeout(20.seconds)), 21.seconds)
}

View file

@ -1,7 +1,7 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.testkit
package akka.testkit.typed
import com.typesafe.config.Config
import scala.concurrent.duration.FiniteDuration

View file

@ -1,15 +1,15 @@
/**
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.testkit.javadsl
package akka.testkit.typed.javadsl
import akka.actor.typed.ActorSystem
import akka.typed.testkit.TestKitSettings
import akka.testkit.typed.TestKitSettings
/**
* Java API:
*/
class TestProbe[M](name: String, system: ActorSystem[_], settings: TestKitSettings) extends akka.typed.testkit.scaladsl.TestProbe[M](name)(system, settings) {
class TestProbe[M](name: String, system: ActorSystem[_], settings: TestKitSettings) extends akka.testkit.typed.scaladsl.TestProbe[M](name)(system, settings) {
def this(system: ActorSystem[_], settings: TestKitSettings) = this("testProbe", system, settings)

View file

@ -1,7 +1,7 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.testkit.scaladsl
package akka.testkit.typed.scaladsl
import scala.concurrent.duration._
import java.util.concurrent.BlockingDeque
@ -18,7 +18,7 @@ import akka.util.PrettyDuration.PrettyPrintableDuration
import scala.concurrent.Await
import com.typesafe.config.Config
import akka.typed.testkit.TestKitSettings
import akka.testkit.typed.TestKitSettings
import akka.util.BoxedType
import scala.annotation.tailrec

View file

@ -1,7 +1,7 @@
/**
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.testkit
package akka.testkit.typed
import scala.concurrent.duration.{ Duration, FiniteDuration }
import scala.reflect.ClassTag
@ -18,7 +18,7 @@ package object scaladsl {
*
* {{{
* import scala.concurrent.duration._
* import akka.typed.testkit.scaladsl._
* import akka.testkit.typed.scaladsl._
* 10.milliseconds.dilated
* }}}
*

View file

@ -1,128 +0,0 @@
/**
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.testkit
import java.util.concurrent.ConcurrentLinkedQueue
import akka.actor.typed.{ ActorContext, ActorRef, ActorSystem, Behavior, PostStop, Props, Signal }
import scala.annotation.tailrec
import scala.collection.immutable
import scala.util.control.Exception.Catcher
import scala.util.control.NonFatal
import scala.concurrent.duration.{ Duration, FiniteDuration }
/**
* All tracked effects must extend implement this type. It is deliberately
* not sealed in order to allow extensions.
*/
abstract class Effect
object Effect {
abstract class SpawnedEffect extends Effect
@SerialVersionUID(1L) final case class Spawned(childName: String, props: Props) extends SpawnedEffect
@SerialVersionUID(1L) final case class SpawnedAnonymous(props: Props) extends SpawnedEffect
@SerialVersionUID(1L) final case object SpawnedAdapter extends SpawnedEffect
@SerialVersionUID(1L) final case class Stopped(childName: String) extends Effect
@SerialVersionUID(1L) final case class Watched[T](other: ActorRef[T]) extends Effect
@SerialVersionUID(1L) final case class Unwatched[T](other: ActorRef[T]) extends Effect
@SerialVersionUID(1L) final case class ReceiveTimeoutSet[T](d: Duration, msg: T) extends Effect
@SerialVersionUID(1L) final case class Messaged[U](other: ActorRef[U], msg: U) extends Effect
@SerialVersionUID(1L) final case class Scheduled[U](delay: FiniteDuration, target: ActorRef[U], msg: U) extends Effect
@SerialVersionUID(1L) case object EmptyEffect extends Effect
}
/**
* An [[ActorContext]] for testing purposes that records the effects performed
* on it and otherwise stubs them out like a [[StubbedActorContext]].
*/
class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _mailboxCapacity: Int, _system: ActorSystem[Nothing])
extends StubbedActorContext[T](_name, _mailboxCapacity, _system) {
import Effect._
import akka.{ actor a }
private val effectQueue = new ConcurrentLinkedQueue[Effect]
def getEffect(): Effect = effectQueue.poll() match {
case null throw new NoSuchElementException(s"polling on an empty effect queue: $name")
case x x
}
def getAllEffects(): immutable.Seq[Effect] = {
@tailrec def rec(acc: List[Effect]): List[Effect] = effectQueue.poll() match {
case null acc.reverse
case x rec(x :: acc)
}
rec(Nil)
}
def hasEffects: Boolean = effectQueue.peek() != null
private var current = Behavior.validateAsInitial(Behavior.undefer(_initialBehavior, this))
def currentBehavior: Behavior[T] = current
def isAlive: Boolean = Behavior.isAlive(current)
private def handleException: Catcher[Unit] = {
case NonFatal(e)
try Behavior.canonicalize(Behavior.interpretSignal(current, this, PostStop), current, this) // TODO why canonicalize here?
catch { case NonFatal(ex) /* ignore, real is logging */ }
throw e
}
def run(msg: T): Unit = {
try {
current = Behavior.canonicalize(Behavior.interpretMessage(current, this, msg), current, this)
} catch handleException
}
def signal(signal: Signal): Unit = {
try {
current = Behavior.canonicalize(Behavior.interpretSignal(current, this, signal), current, this)
} catch handleException
}
override def spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty): ActorRef[U] = {
val ref = super.spawnAnonymous(behavior, props)
effectQueue.offer(SpawnedAnonymous(props))
ref
}
override def spawnAdapter[U](f: U T): ActorRef[U] = {
spawnAdapter(f, "")
}
override def spawnAdapter[U](f: U T, name: String): ActorRef[U] = {
val ref = super.spawnAdapter(f, name)
effectQueue.offer(SpawnedAdapter)
ref
}
override def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty): ActorRef[U] = {
effectQueue.offer(Spawned(name, props))
super.spawn(behavior, name, props)
}
override def stop[U](child: ActorRef[U]): Boolean = {
effectQueue.offer(Stopped(child.path.name))
super.stop(child)
}
override def watch[U](other: ActorRef[U]): Unit = {
effectQueue.offer(Watched(other))
super.watch(other)
}
override def unwatch[U](other: ActorRef[U]): Unit = {
effectQueue.offer(Unwatched(other))
super.unwatch(other)
}
override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = {
effectQueue.offer(ReceiveTimeoutSet(d, msg))
super.setReceiveTimeout(d, msg)
}
override def cancelReceiveTimeout(): Unit = {
effectQueue.offer(ReceiveTimeoutSet(Duration.Undefined, null))
super.cancelReceiveTimeout()
}
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): a.Cancellable = {
effectQueue.offer(Scheduled(delay, target, msg))
super.schedule(delay, target, msg)
}
}

View file

@ -2,16 +2,16 @@
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.testkit
package akka.testkit.typed
import akka.actor.typed.scaladsl.Actor
import akka.typed.testkit.Effect.{ Spawned, SpawnedAdapter, SpawnedAnonymous }
import akka.typed.testkit.EffectfulActorContextSpec.Father
import akka.typed.testkit.EffectfulActorContextSpec.Father._
import akka.actor.typed.{ ActorSystem, Behavior, Props }
import akka.testkit.typed.Effect.{ Spawned, SpawnedAdapter, SpawnedAnonymous }
import akka.testkit.typed.BehaviorTestkitSpec.{ Child, Father }
import akka.testkit.typed.BehaviorTestkitSpec.Father._
import akka.actor.typed.{ Behavior, Props }
import org.scalatest.{ FlatSpec, Matchers }
object EffectfulActorContextSpec {
object BehaviorTestkitSpec {
object Father {
case class Reproduce(times: Int)
@ -67,7 +67,7 @@ object EffectfulActorContextSpec {
sealed trait Action
def initial: Behavior[Action] = Actor.immutable[Action] { (_, msg)
val initial: Behavior[Action] = Actor.immutable[Action] { (_, msg)
msg match {
case _
Actor.empty
@ -78,61 +78,56 @@ object EffectfulActorContextSpec {
}
class EffectfulActorContextSpec extends FlatSpec with Matchers {
//TODO WordSpec
class BehaviorTestkitSpec extends FlatSpec with Matchers {
private val props = Props.empty
"EffectfulActorContext's spawn" should "create children when no props specified" in {
val system = ActorSystem.create(Father.init(), "father-system")
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
"BehaviourTestkit's spawn" should "create children when no props specified" in {
val ctx = BehaviorTestkit[Father.Command](Father.init())
ctx.run(SpawnChildren(2))
val effects = ctx.getAllEffects()
effects should contain only (Spawned("child0", Props.empty), Spawned("child1", Props.empty))
val effects = ctx.retrieveAllEffects()
effects should contain only (Spawned(Child.initial, "child0"), Spawned(Child.initial, "child1", Props.empty))
}
it should "create children when props specified and record effects" in {
val system = ActorSystem.create(Father.init(), "father-system")
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
val ctx = BehaviorTestkit[Father.Command](Father.init())
ctx.run(SpawnChildrenWithProps(2, props))
val effects = ctx.getAllEffects()
effects should contain only (Spawned("child0", props), Spawned("child1", props))
val effects = ctx.retrieveAllEffects()
effects should contain only (Spawned(Child.initial, "child0", props), Spawned(Child.initial, "child1", props))
}
"EffectfulActorContext's spawnAnonymous" should "create children when no props specified and record effects" in {
val system = ActorSystem.create(Father.init(), "father-system")
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
"BehaviourTestkit's spawnAnonymous" should "create children when no props specified and record effects" in {
val ctx = BehaviorTestkit[Father.Command](Father.init())
ctx.run(SpawnAnonymous(2))
val effects = ctx.getAllEffects()
effects shouldBe Seq(SpawnedAnonymous(Props.empty), SpawnedAnonymous(Props.empty))
val effects = ctx.retrieveAllEffects()
effects shouldBe Seq(SpawnedAnonymous(Child.initial, Props.empty), SpawnedAnonymous(Child.initial, Props.empty))
}
it should "create children when props specified and record effects" in {
val system = ActorSystem.create(Father.init(), "father-system")
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
val ctx = BehaviorTestkit[Father.Command](Father.init())
ctx.run(SpawnAnonymousWithProps(2, props))
val effects = ctx.getAllEffects()
effects shouldBe Seq(SpawnedAnonymous(props), SpawnedAnonymous(props))
val effects = ctx.retrieveAllEffects()
effects shouldBe Seq(SpawnedAnonymous(Child.initial, props), SpawnedAnonymous(Child.initial, props))
}
"EffectfulActorContext's spawnAdapter" should "create adapters without name and record effects" in {
val system = ActorSystem.create(Father.init(), "father-system")
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
"BehaviourTestkit's spawnAdapter" should "create adapters without name and record effects" in {
val ctx = BehaviorTestkit[Father.Command](Father.init())
ctx.run(SpawnAdapter)
val effects = ctx.getAllEffects()
val effects = ctx.retrieveAllEffects()
effects shouldBe Seq(SpawnedAdapter)
}
it should "create adapters with name and record effects" in {
val system = ActorSystem.create(Father.init(), "father-system")
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
val ctx = BehaviorTestkit[Father.Command](Father.init())
ctx.run(SpawnAdapterWithName("adapter"))
val effects = ctx.getAllEffects()
val effects = ctx.retrieveAllEffects()
effects shouldBe Seq(SpawnedAdapter)
}
}