add TestKit.setAutoPilot, see #1807

This commit is contained in:
Roland 2012-02-19 00:09:04 +01:00
parent b5826f9bd9
commit ec15fd6cfd
3 changed files with 67 additions and 3 deletions

View file

@ -421,6 +421,14 @@ using a small example:
.. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala
:include: imports-test-probe,my-double-echo,test-probe
Here a the system under test is simulated by :class:`MyDoubleEcho`, which is
supposed to mirror its input to two outputs. Attaching two test probes enables
verification of the (simplistic) behavior. Another example would be two actors
A and B which collaborate by A sending messages to B. In order to verify this
message flow, a :class:`TestProbe` could be inserted as target of A, using the
forwarding capabilities or auto-pilot described below to include a real B in
the test setup.
Probes may also be equipped with custom assertions to make your test code even
more concise and clear:
@ -455,6 +463,21 @@ network functioning:
The ``dest`` actor will receive the same message invocation as if no test probe
had intervened.
Auto-Pilot
^^^^^^^^^^
Receiving messages in a queue for later inspection is nice, but in order to
keep a test running and verify traces later you can also install an
:class:`AutoPilot` in the participating test probes (actually in any
:class:`TestKit`) which is invoked before enqueueing to the inspection queue.
This code can be used to forward messages, e.g. in a chain ``A --> Probe -->
B``, as long as a certain protocol is obeyed.
.. includecode:: ../../akka-testkit/src/test/scala/akka/testkit/TestProbeSpec.scala#autopilot
The :meth:`run` method must return the auto-pilot for the next message, wrapped
in an :class:`Option`; setting it to :obj:`None` terminates the auto-pilot.
Caution about Timing Assertions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -16,9 +16,14 @@ import akka.util.Timeout
object TestActor {
type Ignore = Option[PartialFunction[AnyRef, Boolean]]
trait AutoPilot {
def run(sender: ActorRef, msg: Any): Option[AutoPilot]
}
case class SetIgnore(i: Ignore)
case class Watch(ref: ActorRef)
case class UnWatch(ref: ActorRef)
case class SetAutoPilot(ap: AutoPilot)
trait Message {
def msg: AnyRef
@ -36,11 +41,15 @@ class TestActor(queue: BlockingDeque[TestActor.Message]) extends Actor {
var ignore: Ignore = None
var autopilot: Option[AutoPilot] = None
def receive = {
case SetIgnore(ign) ignore = ign
case x @ Watch(ref) context.watch(ref); queue.offerLast(RealMessage(x, self))
case x @ UnWatch(ref) context.unwatch(ref); queue.offerLast(RealMessage(x, self))
case SetAutoPilot(pilot) autopilot = Some(pilot)
case x: AnyRef
autopilot = autopilot.flatMap(_.run(sender, x))
val observe = ignore map (ignoreFunc if (ignoreFunc isDefinedAt x) !ignoreFunc(x) else true) getOrElse true
if (observe) queue.offerLast(RealMessage(x, sender))
}
@ -148,6 +157,13 @@ class TestKit(_system: ActorSystem) {
expectMsg(msg)
}
/**
* Install an AutoPilot to drive the testActor: the AutoPilot will be run
* for each received message and can be used to send or forward messages,
* etc. Each invocation must return the AutoPilot for the next round.
*/
def setAutoPilot(pilot: TestActor.AutoPilot): Unit = testActor ! TestActor.SetAutoPilot(pilot)
/**
* Obtain current time (`System.nanoTime`) as Duration.
*/

View file

@ -40,6 +40,31 @@ class TestProbeSpec extends AkkaSpec with DefaultTimeout {
probe1.expectMsg(0 millis, "world")
}
"have an AutoPilot" in {
//#autopilot
val probe = TestProbe()
probe.setAutoPilot(new TestActor.AutoPilot {
def run(sender: ActorRef, msg: Any): Option[TestActor.AutoPilot] =
msg match {
case "stop" None
case x testActor.tell(x, sender); Some(this)
}
})
//#autopilot
probe.ref ! "hallo"
probe.ref ! "welt"
probe.ref ! "stop"
expectMsg("hallo")
expectMsg("welt")
probe.expectMsg("hallo")
probe.expectMsg("welt")
probe.expectMsg("stop")
probe.ref ! "hallo"
probe.expectMsg("hallo")
testActor ! "end"
expectMsg("end") // verify that "hallo" did not get through
}
}
}