diff --git a/akka-docs/scala/code/akka/docs/testkit/PlainWordSpec.scala b/akka-docs/scala/code/akka/docs/testkit/PlainWordSpec.scala new file mode 100644 index 0000000000..6ca6e5c448 --- /dev/null +++ b/akka-docs/scala/code/akka/docs/testkit/PlainWordSpec.scala @@ -0,0 +1,44 @@ +package akka.docs.testkit + +//#plain-spec +import akka.actor.ActorSystem +import akka.actor.Actor +import akka.actor.Props +import akka.testkit.TestKit +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers +import org.scalatest.BeforeAndAfterAll +import akka.testkit.ImplicitSender + +object MySpec { + class EchoActor extends Actor { + def receive = { + case x ⇒ sender ! x + } + } +} + +//#implicit-sender +class MySpec(_system: ActorSystem) extends TestKit(_system) with ImplicitSender + with WordSpec with MustMatchers with BeforeAndAfterAll { + //#implicit-sender + + def this() = this(ActorSystem("MySpec")) + + import MySpec._ + + override def afterAll { + system.shutdown() + } + + "An Echo actor" must { + + "send back messages unchanged" in { + val echo = system.actorOf(Props[EchoActor]) + echo ! "hello world" + expectMsg("hello world") + } + + } +} +//#plain-spec \ No newline at end of file diff --git a/akka-docs/scala/code/akka/docs/testkit/TestkitDocSpec.scala b/akka-docs/scala/code/akka/docs/testkit/TestkitDocSpec.scala new file mode 100644 index 0000000000..2fa450e0dd --- /dev/null +++ b/akka-docs/scala/code/akka/docs/testkit/TestkitDocSpec.scala @@ -0,0 +1,238 @@ +package akka.docs.testkit + +//#imports-test-probe +import akka.testkit.TestProbe +import akka.actor.Actor +import akka.actor.ActorRef +import akka.actor.Props +import akka.util.duration._ + +//#imports-test-probe + +import akka.testkit.AkkaSpec +import akka.actor.Actor +import akka.testkit.DefaultTimeout +import akka.testkit.ImplicitSender +import akka.actor.ActorRef +import akka.actor.Props + +object TestkitDocSpec { + case object Say42 + case object Unknown + + class MyActor extends Actor { + def receive = { + case Say42 ⇒ sender ! 42 + case "some work" ⇒ sender ! "some result" + } + } + + //#my-double-echo + class MyDoubleEcho extends Actor { + var dest1: ActorRef = _ + var dest2: ActorRef = _ + def receive = { + case (d1: ActorRef, d2: ActorRef) ⇒ + dest1 = d1 + dest2 = d2 + case x ⇒ + dest1 ! x + dest2 ! x + } + } + + //#my-double-echo + + import akka.testkit.TestProbe + + //#test-probe-forward-actors + class Source(target: ActorRef) extends Actor { + def receive = { + case "start" ⇒ target ! "work" + } + } + + class Destination extends Actor { + def receive = { + case x ⇒ // Do something.. + } + } + + //#test-probe-forward-actors + + class LoggingActor extends Actor { + //#logging-receive + import akka.event.LoggingReceive + implicit def system = context.system + def receive = LoggingReceive(this) { + case msg ⇒ // Do something... + } + //#logging-receive + } +} + +class TestkitDocSpec extends AkkaSpec with DefaultTimeout with ImplicitSender { + import TestkitDocSpec._ + + "demonstrate usage of TestActorRef" in { + //#test-actor-ref + import akka.testkit.TestActorRef + + val actorRef = TestActorRef[MyActor] + val actor = actorRef.underlyingActor + //#test-actor-ref + } + + "demonstrate usage of TestFSMRef" in { + //#test-fsm-ref + import akka.testkit.TestFSMRef + import akka.actor.FSM + import akka.util.duration._ + + val fsm = TestFSMRef(new Actor with FSM[Int, String] { + startWith(1, "") + when(1) { + case Ev("go") ⇒ goto(2) using "go" + } + when(2) { + case Ev("back") ⇒ goto(1) using "back" + } + }) + + assert(fsm.stateName == 1) + assert(fsm.stateData == "") + fsm ! "go" // being a TestActorRef, this runs also on the CallingThreadDispatcher + assert(fsm.stateName == 2) + assert(fsm.stateData == "go") + + fsm.setState(stateName = 1) + assert(fsm.stateName == 1) + + assert(fsm.timerActive_?("test") == false) + fsm.setTimer("test", 12, 10 millis, true) + assert(fsm.timerActive_?("test") == true) + fsm.cancelTimer("test") + assert(fsm.timerActive_?("test") == false) + //#test-fsm-ref + } + + "demonstrate testing of behavior" in { + + //#test-behavior + import akka.testkit.TestActorRef + import akka.util.duration._ + import akka.dispatch.Await + + val actorRef = TestActorRef(new MyActor) + // hypothetical message stimulating a '42' answer + val result = Await.result((actorRef ? Say42), 5 seconds).asInstanceOf[Int] + result must be(42) + //#test-behavior + } + + "demonstrate unhandled message" in { + //#test-unhandled + import akka.testkit.TestActorRef + import akka.actor.UnhandledMessageException + + val ref = TestActorRef[MyActor] + intercept[UnhandledMessageException] { ref(Unknown) } + //#test-unhandled + } + + "demonstrate expecting exceptions" in { + //#test-expecting-exceptions + import akka.testkit.TestActorRef + + val actorRef = TestActorRef(new Actor { + def receive = { + case boom ⇒ throw new IllegalArgumentException("boom") + } + }) + intercept[IllegalArgumentException] { actorRef("hello") } + //#test-expecting-exceptions + } + + "demonstrate within" in { + type Worker = MyActor + //#test-within + import akka.actor.Props + import akka.util.duration._ + + val worker = system.actorOf(Props[Worker]) + within(200 millis) { + worker ! "some work" + expectMsg("some result") + expectNoMsg // will block for the rest of the 200ms + Thread.sleep(300) // will NOT make this block fail + } + //#test-within + } + + "demonstrate dilated duration" in { + //#duration-dilation + import akka.util.duration._ + import akka.testkit._ + 10.milliseconds.dilated + //#duration-dilation + } + + "demonstrate usage of probe" in { + //#test-probe + val probe1 = TestProbe() + val probe2 = TestProbe() + val actor = system.actorOf(Props[MyDoubleEcho]) + actor ! (probe1.ref, probe2.ref) + actor ! "hello" + probe1.expectMsg(50 millis, "hello") + probe2.expectMsg(50 millis, "hello") + //#test-probe + + //#test-special-probe + case class Update(id: Int, value: String) + + val probe = new TestProbe(system) { + def expectUpdate(x: Int) = { + expectMsgPF() { + case Update(id, _) if id == x ⇒ true + } + sender ! "ACK" + } + } + //#test-special-probe + } + + "demonstrate probe reply" in { + import akka.testkit.TestProbe + import akka.util.duration._ + //#test-probe-reply + val probe = TestProbe() + val future = probe.ref ? "hello" + probe.expectMsg(0 millis, "hello") // TestActor runs on CallingThreadDispatcher + probe.sender ! "world" + assert(future.isCompleted && future.value == Some(Right("world"))) + //#test-probe-reply + } + + "demonstrate probe forward" in { + import akka.testkit.TestProbe + import akka.actor.Props + //#test-probe-forward + val probe = TestProbe() + val source = system.actorOf(Props(new Source(probe.ref))) + val dest = system.actorOf(Props[Destination]) + source ! "start" + probe.expectMsg("work") + probe.forward(dest) + //#test-probe-forward + } + + "demonstrate " in { + //#calling-thread-dispatcher + import akka.testkit.CallingThreadDispatcher + val dispatcher = new CallingThreadDispatcher(system.dispatcherFactory.prerequisites) + val ref = system.actorOf(Props[MyActor].withDispatcher(dispatcher)) + //#calling-thread-dispatcher + } + +} diff --git a/akka-docs/scala/testing.rst b/akka-docs/scala/testing.rst index 7439a0af8a..c459e9055f 100644 --- a/akka-docs/scala/testing.rst +++ b/akka-docs/scala/testing.rst @@ -15,11 +15,6 @@ Testing Actor Systems .. module:: akka-testkit :synopsis: Tools for Testing Actor Systems .. moduleauthor:: Roland Kuhn -.. versionadded:: 1.0 -.. versionchanged:: 1.1 - added :class:`TestActorRef` -.. versionchanged:: 1.2 - added :class:`TestFSMRef` As with any piece of software, automated tests are a very important part of the development cycle. The actor model presents a different view on how units of @@ -74,12 +69,7 @@ Having access to the actual :class:`Actor` object allows application of all traditional unit testing techniques on the contained methods. Obtaining a reference is done like this: -.. code-block:: scala - - import akka.testkit.TestActorRef - - val actorRef = TestActorRef[MyActor] - val actor = actorRef.underlyingActor +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala#test-actor-ref Since :class:`TestActorRef` is generic in the actor type it returns the underlying actor with its proper static type. From this point on you may bring @@ -92,35 +82,9 @@ Testing Finite State Machines If your actor under test is a :class:`FSM`, you may use the special :class:`TestFSMRef` which offers all features of a normal :class:`TestActorRef` -and in addition allows access to the internal state:: +and in addition allows access to the internal state: - import akka.testkit.TestFSMRef - import akka.util.duration._ - - val fsm = TestFSMRef(new Actor with FSM[Int, String] { - startWith(1, "") - when (1) { - case Ev("go") => goto(2) using "go" - } - when (2) { - case Ev("back") => goto(1) using "back" - } - }) - - assert (fsm.stateName == 1) - assert (fsm.stateData == "") - fsm ! "go" // being a TestActorRef, this runs also on the CallingThreadDispatcher - assert (fsm.stateName == 2) - assert (fsm.stateData == "go") - - fsm.setState(stateName = 1) - assert (fsm.stateName == 1) - - assert (fsm.timerActive_?("test") == false) - fsm.setTimer("test", 12, 10 millis, true) - assert (fsm.timerActive_?("test") == true) - fsm.cancelTimer("test") - assert (fsm.timerActive_?("test") == false) +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala#test-fsm-ref Due to a limitation in Scala’s type inference, there is only the factory method shown above, so you will probably write code like ``TestFSMRef(new MyFSM)`` @@ -150,11 +114,7 @@ usual. This trick is made possible by the :class:`CallingThreadDispatcher` described below; this dispatcher is set implicitly for any actor instantiated into a :class:`TestActorRef`. -.. code-block:: scala - - val actorRef = TestActorRef(new MyActor).start() - val result = (actorRef ? Say42).as[Int] // hypothetical message stimulating a '42' answer - result must be (Some(42)) +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala#test-behavior As the :class:`TestActorRef` is a subclass of :class:`LocalActorRef` with a few special extras, also aspects like linking to a supervisor and restarting work @@ -172,7 +132,7 @@ One more special aspect which is overridden for single-threaded tests is the To summarize: :class:`TestActorRef` overwrites two fields: it sets the dispatcher to :obj:`CallingThreadDispatcher.global` and it sets the - :obj:`receiveTimeout` to zero. + :obj:`receiveTimeout` to None. The Way In-Between ------------------ @@ -180,15 +140,13 @@ The Way In-Between If you want to test the actor behavior, including hotswapping, but without involving a dispatcher and without having the :class:`TestActorRef` swallow any thrown exceptions, then there is another mode available for you: just use -the :class:`TestActorRef` as a partial function, the calls to -:meth:`isDefinedAt` and :meth:`apply` will be forwarded to the underlying -actor: +the :meth:`apply` method :class:`TestActorRef`, which will be forwarded to the +underlying actor: -.. code-block:: scala +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala#test-unhandled - val ref = TestActorRef[MyActor] - ref.isDefinedAt('unknown) must be (false) - intercept[IllegalActorStateException] { ref(RequestReply) } +The above sample assumes the default behavior for unhandled messages, i.e. +that the actor doesn't swallow all messages and doesn't override :meth:`unhandled`. Use Cases --------- @@ -221,33 +179,13 @@ 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. -The :class:`TestKit` trait contains a collection of tools which makes this -common task easy: +The :class:`TestKit` class contains a collection of tools which makes this +common task easy. -.. code-block:: scala +.. includecode:: code/akka/docs/testkit/PlainWordSpec.scala#plain-spec - import akka.testkit.TestKit - import org.scalatest.WordSpec - import org.scalatest.matchers.MustMatchers - - class MySpec extends WordSpec with MustMatchers with TestKit { - - "An Echo actor" must { - - "send back messages unchanged" in { - - val echo = Actor.actorOf(Props[EchoActor]) - echo ! "hello world" - expectMsg("hello world") - - } - - } - - } - -The :class:`TestKit` contains an actor named :obj:`testActor` which is -implicitly used as sender reference when dispatching messages from the test +When using ``with ImplicitSender`` the :class:`TestKit` contains an actor named :obj:`testActor` +which is implicitly used as sender reference when dispatching messages from the test procedure. This enables replies to be received by this internal actor, whose only function is to queue them so that interrogation methods like :meth:`expectMsg` can examine them. The :obj:`testActor` may also be passed to @@ -256,6 +194,9 @@ is a whole set of examination methods, e.g. receiving all consecutive messages matching certain criteria, receiving a whole sequence of fixed messages or classes, receiving nothing for some time, etc. +To avoid memory leaks it is important that you shutdown the :class:`ActorSystem` +when the test is finished, as illustrated in the :meth:`afterAll` method above. + .. note:: The test actor shuts itself down by default after 5 seconds (configurable) @@ -385,15 +326,11 @@ with message flows: Expecting Exceptions -------------------- -One case which is not handled by the :obj:`testActor` is if an exception is -thrown while processing the message sent to the actor under test. This can be -tested by using a :class:`Future` based invocation:: +Testing that an expected exception is thrown while processing a message sent to +the actor under test can be done by using a :class:`TestActorRef` :meth:`apply` based +invocation: - // assuming ScalaTest ShouldMatchers - - evaluating { - (someActor ? badOperation).await.get - } should produce [UnhandledMessageException] +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala#test-expecting-exceptions .. _TestKit.within: @@ -426,21 +363,7 @@ It should be noted that if the last message-receiving assertion of the block is latencies. This means that while individual contained assertions still use the maximum time bound, the overall block may take arbitrarily longer in this case. -.. code-block:: scala - - class SomeSpec extends WordSpec with MustMatchers with TestKit { - "A Worker" must { - "send timely replies" in { - val worker = ActorSystem().actorOf(...) - within (500 millis) { - worker ! "some work" - expectMsg("some result") - expectNoMsg // will block for the rest of the 500ms - Thread.sleep(1000) // will NOT make this block fail - } - } - } - } +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala#test-within .. note:: @@ -460,25 +383,18 @@ invariably lead to spurious test failures on the heavily loaded Jenkins server internally scaled by a factor taken from the :ref:`configuration`, ``akka.test.timefactor``, which defaults to 1. +You can scale other durations with the same factor by using the implicit conversion +in ``akka.testkit`` package object to add dilated function to :class:`Duration`. + +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala#duration-dilation + Resolving Conflicts with Implicit ActorRef ------------------------------------------ -If you want the sender of messages inside your TestKit-based tests to be the `testActor``` -simply mix in `ÌmplicitSender`` into your test. +If you want the sender of messages inside your TestKit-based tests to be the ``testActor`` +simply mix in ``ÌmplicitSender`` into your test. -.. code-block:: scala - - class SomeSpec extends WordSpec with MustMatchers with TestKit with ImplicitSender { - "A Worker" must { - "send timely replies" in { - val worker = ActorSystem().actorOf(...) - within (500 millis) { - worker ! "some work" // testActor is the "sender" for this message - expectMsg("some result") - } - } - } - } +.. includecode:: code/akka/docs/testkit/PlainWordSpec.scala#implicit-sender Using Multiple Probe Actors --------------------------- @@ -489,42 +405,16 @@ at the :obj:`testActor` when using the :class:`TestKit` as a mixin. Another approach is to use it for creation of simple probe actors to be inserted in the message flows. To make this more powerful and convenient, there is a concrete implementation called :class:`TestProbe`. The functionality is best explained -using a small example:: +using a small example: - class MyDoubleEcho extends Actor { - var dest1 : ActorRef = _ - var dest2 : ActorRef = _ - def receive = { - case (d1: ActorRef, d2: ActorRef) => - dest1 = d1 - dest2 = d2 - case x => - dest1 ! x - dest2 ! x - } - } - - val probe1 = TestProbe() - val probe2 = TestProbe() - val actor = ActorSystem().actorOf(Props[MyDoubleEcho]) - actor ! (probe1.ref, probe2.ref) - actor ! "hello" - probe1.expectMsg(50 millis, "hello") - probe2.expectMsg(50 millis, "hello") +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala + :include: imports-test-probe,my-double-echo,test-probe Probes may also be equipped with custom assertions to make your test code even -more concise and clear:: +more concise and clear: - case class Update(id : Int, value : String) - - val probe = new TestProbe { - def expectUpdate(x : Int) = { - expectMsg { - case Update(id, _) if id == x => true - } - reply("ACK") - } - } +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala + :include: test-special-probe You have complete flexibility here in mixing and matching the :class:`TestKit` facilities with your own checks and choosing an intuitive name for it. In real @@ -535,13 +425,9 @@ Replying to Messages Received by Probes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The probes keep track of the communications channel for replies, if possible, -so they can also reply:: +so they can also reply: - val probe = TestProbe() - val future = probe.ref ? "hello" - probe.expectMsg(0 millis, "hello") // TestActor runs on CallingThreadDispatcher - probe.reply("world") - assert (future.isCompleted && future.as[String] == "world") +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala#test-probe-reply Forwarding Messages Received by Probes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -550,15 +436,10 @@ Given a destination actor ``dest`` which in the nominal actor network would receive a message from actor ``source``. If you arrange for the message to be sent to a :class:`TestProbe` ``probe`` instead, you can make assertions concerning volume and timing of the message flow while still keeping the -network functioning:: +network functioning: - val probe = TestProbe() - val system = ActorSystem() - val source = system.actorOf(Props(new Source(probe))) - val dest = system.actorOf(Props[Destination]) - source ! "start" - probe.expectMsg("work") - probe.forward(dest) +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala + :include: test-probe-forward-actors,test-probe-forward The ``dest`` actor will receive the same message invocation as if no test probe had intervened. @@ -572,7 +453,7 @@ described :ref:`above ` is local to each probe. Hence, probes do not react to each other's deadlines or to the deadline set in an enclosing :class:`TestKit` instance:: - class SomeTest extends TestKit { + class SomeTest extends TestKit(_system: ActorSystem) with ImplicitSender { val probe = TestProbe() @@ -599,25 +480,9 @@ so long as all intervening actors run on this dispatcher. How to use it ------------- -Just set the dispatcher as you normally would, either from within the actor +Just set the dispatcher as you normally would: -.. code-block:: scala - - import akka.testkit.CallingThreadDispatcher - - class MyActor extends Actor { - self.dispatcher = CallingThreadDispatcher.global - ... - } - -or from the client code - -.. code-block:: scala - - val ref = system.actorOf(Props[MyActor].withDispatcher(CallingThreadDispatcher.global)) - -As the :class:`CallingThreadDispatcher` does not have any configurable state, -you may always use the (lazily) preallocated one as shown in the examples. +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala#calling-thread-dispatcher How it works ------------ @@ -719,13 +584,11 @@ options: This is enabled by a setting in the :ref:`configuration` — namely ``akka.actor.debug.receive`` — which enables the :meth:`loggable` - statement to be applied to an actor’s :meth:`receive` function:: + statement to be applied to an actor’s :meth:`receive` function: - import akka.event.LoggingReceive - def receive = LoggingReceive(this) { - case msg => ... - } +.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala#logging-receive +. The first argument to :meth:`LoggingReceive` defines the source to be used in the logging events, which should be the current actor. @@ -755,12 +618,12 @@ All these messages are logged at ``DEBUG`` level. To summarize, you can enable full logging of actor activities using this configuration fragment:: akka { - loglevel = "DEBUG" + loglevel = DEBUG actor { debug { - receive = "true" - autoreceive = "true" - lifecycle = "true" + receive = on + autoreceive = on + lifecycle = on } } }