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 .. includecode:: code/akka/docs/testkit/TestkitDocSpec.scala
:include: imports-test-probe,my-double-echo,test-probe :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 Probes may also be equipped with custom assertions to make your test code even
more concise and clear: more concise and clear:
@ -455,6 +463,21 @@ network functioning:
The ``dest`` actor will receive the same message invocation as if no test probe The ``dest`` actor will receive the same message invocation as if no test probe
had intervened. 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 Caution about Timing Assertions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -16,9 +16,14 @@ import akka.util.Timeout
object TestActor { object TestActor {
type Ignore = Option[PartialFunction[AnyRef, Boolean]] type Ignore = Option[PartialFunction[AnyRef, Boolean]]
trait AutoPilot {
def run(sender: ActorRef, msg: Any): Option[AutoPilot]
}
case class SetIgnore(i: Ignore) case class SetIgnore(i: Ignore)
case class Watch(ref: ActorRef) case class Watch(ref: ActorRef)
case class UnWatch(ref: ActorRef) case class UnWatch(ref: ActorRef)
case class SetAutoPilot(ap: AutoPilot)
trait Message { trait Message {
def msg: AnyRef def msg: AnyRef
@ -36,11 +41,15 @@ class TestActor(queue: BlockingDeque[TestActor.Message]) extends Actor {
var ignore: Ignore = None var ignore: Ignore = None
var autopilot: Option[AutoPilot] = None
def receive = { def receive = {
case SetIgnore(ign) ignore = ign case SetIgnore(ign) ignore = ign
case x @ Watch(ref) context.watch(ref); queue.offerLast(RealMessage(x, self)) 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 x @ UnWatch(ref) context.unwatch(ref); queue.offerLast(RealMessage(x, self))
case SetAutoPilot(pilot) autopilot = Some(pilot)
case x: AnyRef case x: AnyRef
autopilot = autopilot.flatMap(_.run(sender, x))
val observe = ignore map (ignoreFunc if (ignoreFunc isDefinedAt x) !ignoreFunc(x) else true) getOrElse true val observe = ignore map (ignoreFunc if (ignoreFunc isDefinedAt x) !ignoreFunc(x) else true) getOrElse true
if (observe) queue.offerLast(RealMessage(x, sender)) if (observe) queue.offerLast(RealMessage(x, sender))
} }
@ -148,6 +157,13 @@ class TestKit(_system: ActorSystem) {
expectMsg(msg) 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. * Obtain current time (`System.nanoTime`) as Duration.
*/ */

View file

@ -40,6 +40,31 @@ class TestProbeSpec extends AkkaSpec with DefaultTimeout {
probe1.expectMsg(0 millis, "world") 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
}
} }
} }