=doc #13043 Adding section on testing parent-child relationships
This commit is contained in:
parent
f2f88d9dd7
commit
dd8c920280
4 changed files with 446 additions and 0 deletions
202
akka-docs/rst/java/code/docs/testkit/ParentChildTest.java
Normal file
202
akka-docs/rst/java/code/docs/testkit/ParentChildTest.java
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
package docs.testkit;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import akka.actor.*;
|
||||||
|
import akka.japi.Creator;
|
||||||
|
import akka.japi.Function;
|
||||||
|
import akka.testkit.AkkaJUnitActorSystemResource;
|
||||||
|
import akka.testkit.TestActorRef;
|
||||||
|
import akka.testkit.TestProbe;
|
||||||
|
import com.typesafe.config.ConfigFactory;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ParentChildTest {
|
||||||
|
@ClassRule
|
||||||
|
public static AkkaJUnitActorSystemResource actorSystemResource =
|
||||||
|
new AkkaJUnitActorSystemResource("TestKitDocTest",
|
||||||
|
ConfigFactory.parseString("akka.loggers = [akka.testkit.TestEventListener]"));
|
||||||
|
|
||||||
|
|
||||||
|
private final ActorSystem system = actorSystemResource.getSystem();
|
||||||
|
|
||||||
|
//#test-example
|
||||||
|
static class Parent extends UntypedActor {
|
||||||
|
final ActorRef child = context().actorOf(Props.create(Child.class), "child");
|
||||||
|
boolean ponged = false;
|
||||||
|
|
||||||
|
@Override public void onReceive(Object message) throws Exception {
|
||||||
|
if ("pingit".equals(message)) {
|
||||||
|
child.tell("ping", self());
|
||||||
|
} else if ("pong".equals(message)) {
|
||||||
|
ponged = true;
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Child extends UntypedActor {
|
||||||
|
@Override public void onReceive(Object message) throws Exception {
|
||||||
|
if ("ping".equals(message)) {
|
||||||
|
context().parent().tell("pong", self());
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#test-example
|
||||||
|
|
||||||
|
static
|
||||||
|
//#test-dependentchild
|
||||||
|
class DependentChild extends UntypedActor {
|
||||||
|
private final ActorRef parent;
|
||||||
|
|
||||||
|
public DependentChild(ActorRef parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onReceive(Object message) throws Exception {
|
||||||
|
if ("ping".equals(message)) {
|
||||||
|
parent.tell("pong", self());
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#test-dependentchild
|
||||||
|
|
||||||
|
static
|
||||||
|
//#test-dependentparent
|
||||||
|
class DependentParent extends UntypedActor {
|
||||||
|
final ActorRef child;
|
||||||
|
boolean ponged = false;
|
||||||
|
|
||||||
|
public DependentParent(Props childProps) {
|
||||||
|
child = context().actorOf(childProps, "child");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onReceive(Object message) throws Exception {
|
||||||
|
if ("pingit".equals(message)) {
|
||||||
|
child.tell("ping", self());
|
||||||
|
} else if ("pong".equals(message)) {
|
||||||
|
ponged = true;
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#test-dependentparent
|
||||||
|
|
||||||
|
static
|
||||||
|
//#test-dependentparent-generic
|
||||||
|
class GenericDependentParent extends UntypedActor {
|
||||||
|
final ActorRef child;
|
||||||
|
boolean ponged = false;
|
||||||
|
|
||||||
|
public GenericDependentParent(Function<ActorRefFactory, ActorRef> childMaker)
|
||||||
|
throws Exception {
|
||||||
|
child = childMaker.apply(context());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onReceive(Object message) throws Exception {
|
||||||
|
if ("pingit".equals(message)) {
|
||||||
|
child.tell("ping", self());
|
||||||
|
} else if ("pong".equals(message)) {
|
||||||
|
ponged = true;
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#test-dependentparent-generic
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testingWithoutParent() {
|
||||||
|
TestProbe probe = new TestProbe(system);
|
||||||
|
ActorRef child = system.actorOf(Props.create(DependentChild.class, probe.ref()));
|
||||||
|
probe.send(child, "ping");
|
||||||
|
probe.expectMsg("pong");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testingWithCustomProps() {
|
||||||
|
TestProbe probe = new TestProbe(system);
|
||||||
|
Props childProps = Props.create(MockedChild.class);
|
||||||
|
TestActorRef<DependentParent> parent = TestActorRef.create(system, Props.create(DependentParent.class, childProps));
|
||||||
|
|
||||||
|
probe.send(parent, "pingit");
|
||||||
|
|
||||||
|
// test some parent state change
|
||||||
|
assertTrue(parent.underlyingActor().ponged == true || parent.underlyingActor().ponged == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testingWithChildProbe() throws Exception {
|
||||||
|
final TestProbe probe = new TestProbe(system);
|
||||||
|
//#child-maker-test
|
||||||
|
Function<ActorRefFactory, ActorRef> maker = new Function<ActorRefFactory, ActorRef>() {
|
||||||
|
@Override public ActorRef apply(ActorRefFactory param) throws Exception {
|
||||||
|
return probe.ref();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ActorRef parent = system.actorOf(Props.create(GenericDependentParent.class, maker));
|
||||||
|
//#child-maker-test
|
||||||
|
probe.send(parent, "pingit");
|
||||||
|
probe.expectMsg("ping");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exampleProdActorFactoryFunction() throws Exception {
|
||||||
|
//#child-maker-prod
|
||||||
|
Function<ActorRefFactory, ActorRef> maker = new Function<ActorRefFactory, ActorRef>() {
|
||||||
|
@Override public ActorRef apply(ActorRefFactory f) throws Exception {
|
||||||
|
return f.actorOf(Props.create(Child.class));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ActorRef parent = system.actorOf(Props.create(GenericDependentParent.class, maker));
|
||||||
|
//#child-maker-prod
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
//#test-fabricated-parent-creator
|
||||||
|
class FabricatedParentCreator implements Creator<Actor> {
|
||||||
|
private final TestProbe proxy;
|
||||||
|
|
||||||
|
public FabricatedParentCreator(TestProbe proxy) {
|
||||||
|
this.proxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Actor create() throws Exception {
|
||||||
|
return new UntypedActor() {
|
||||||
|
final ActorRef child = context().actorOf(Props.create(Child.class), "child");
|
||||||
|
|
||||||
|
@Override public void onReceive(Object x) throws Exception {
|
||||||
|
if (sender().equals(child)) {
|
||||||
|
proxy.ref().forward(x, context());
|
||||||
|
} else {
|
||||||
|
child.forward(x, context());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#test-fabricated-parent-creator
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fabricatedParentTestsItsChildResponses() throws Exception {
|
||||||
|
// didn't put final on these in order to make the parent fit in one line in the html docs
|
||||||
|
//#test-fabricated-parent
|
||||||
|
TestProbe proxy = new TestProbe(system);
|
||||||
|
ActorRef parent = system.actorOf(Props.create(new FabricatedParentCreator(proxy)));
|
||||||
|
|
||||||
|
proxy.send(parent, "ping");
|
||||||
|
proxy.expectMsg("pong");
|
||||||
|
//#test-fabricated-parent
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -462,6 +462,58 @@ do not react to each other's deadlines or to the deadline set in an enclosing
|
||||||
|
|
||||||
Here, the ``expectMsgEquals`` call will use the default timeout.
|
Here, the ``expectMsgEquals`` call will use the default timeout.
|
||||||
|
|
||||||
|
Testing parent-child relationships
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
The parent of an actor is always the actor that created it. At times this leads to
|
||||||
|
a coupling between the two that may not be straightforward to test.
|
||||||
|
Broadly, there are three approaches to improve testability of parent-child
|
||||||
|
relationships:
|
||||||
|
|
||||||
|
1. when creating a child, pass an explicit reference to its parent
|
||||||
|
2. when creating a parent, tell the parent how to create its child
|
||||||
|
3. create a fabricated parent when testing
|
||||||
|
|
||||||
|
For example, the structure of the code you want to test may follow this pattern:
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildTest.java#test-example
|
||||||
|
|
||||||
|
Using dependency-injection
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The first option is to avoid use of the :meth:`context.parent` function and create
|
||||||
|
a child with a custom parent by passing an explicit reference to its parent instead.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildTest.java#test-dependentchild
|
||||||
|
|
||||||
|
Alternatively, you can tell the parent how to create its child. There are two ways
|
||||||
|
to do this: by giving it a :class:`Props` object or by giving it a function which takes care of creating the child actor:
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildTest.java#test-dependentparent
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildTest.java#test-dependentparent-generic
|
||||||
|
|
||||||
|
Creating the :class:`Actor` is straightforward and the function may look like this in your test code:
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildTest.java#child-maker-test
|
||||||
|
|
||||||
|
And like this in your application code:
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildTest.java#child-maker-prod
|
||||||
|
|
||||||
|
Using a fabricated parent
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If you prefer to avoid modifying the parent or child constructor you can
|
||||||
|
create a fabricated parent in your test. This, however, does not enable you to test
|
||||||
|
the parent actor in isolation.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildTest.java#test-fabricated-parent-creator
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildTest.java#test-fabricated-parent
|
||||||
|
|
||||||
|
Which of these methods is the best depends on what is most important to test. The
|
||||||
|
most generic option is to create the parent actor by passing it a function that is
|
||||||
|
responsible for the Actor creation, but the fabricated parent is often sufficient.
|
||||||
|
|
||||||
.. _Java-CallingThreadDispatcher:
|
.. _Java-CallingThreadDispatcher:
|
||||||
|
|
||||||
CallingThreadDispatcher
|
CallingThreadDispatcher
|
||||||
|
|
|
||||||
135
akka-docs/rst/scala/code/docs/testkit/ParentChildSpec.scala
Normal file
135
akka-docs/rst/scala/code/docs/testkit/ParentChildSpec.scala
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
package docs.testkit
|
||||||
|
|
||||||
|
import org.scalatest.WordSpec
|
||||||
|
import org.scalatest.Matchers
|
||||||
|
import akka.testkit.TestKitBase
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.actor.Props
|
||||||
|
import akka.actor.Actor
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import akka.testkit.ImplicitSender
|
||||||
|
import akka.testkit.TestProbe
|
||||||
|
import akka.testkit.TestActorRef
|
||||||
|
import akka.actor.ActorRefFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent-Child examples
|
||||||
|
*/
|
||||||
|
|
||||||
|
//#test-example
|
||||||
|
class Parent extends Actor {
|
||||||
|
val child = context.actorOf(Props[Child], "child")
|
||||||
|
var ponged = false
|
||||||
|
|
||||||
|
def receive = {
|
||||||
|
case "pingit" => child ! "ping"
|
||||||
|
case "pong" => ponged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Actor {
|
||||||
|
def receive = {
|
||||||
|
case "ping" => context.parent ! "pong"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#test-example
|
||||||
|
|
||||||
|
//#test-dependentchild
|
||||||
|
class DependentChild(parent: ActorRef) extends Actor {
|
||||||
|
def receive = {
|
||||||
|
case "ping" => parent ! "pong"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#test-dependentchild
|
||||||
|
|
||||||
|
//#test-dependentparent
|
||||||
|
class DependentParent(childProps: Props) extends Actor {
|
||||||
|
val child = context.actorOf(childProps, "child")
|
||||||
|
var ponged = false
|
||||||
|
|
||||||
|
def receive = {
|
||||||
|
case "pingit" => child ! "ping"
|
||||||
|
case "pong" => ponged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GenericDependentParent(childMaker: ActorRefFactory => ActorRef) extends Actor {
|
||||||
|
val child = childMaker(context)
|
||||||
|
var ponged = false
|
||||||
|
|
||||||
|
def receive = {
|
||||||
|
case "pingit" => child ! "ping"
|
||||||
|
case "pong" => ponged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#test-dependentparent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test specification
|
||||||
|
*/
|
||||||
|
|
||||||
|
class MockedChild extends Actor {
|
||||||
|
def receive = {
|
||||||
|
case "ping" => sender ! "pong"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParentChildSpec extends WordSpec with Matchers with TestKitBase {
|
||||||
|
implicit lazy val system = ActorSystem()
|
||||||
|
|
||||||
|
"A DependentChild" should {
|
||||||
|
"be tested without its parent" in {
|
||||||
|
val probe = TestProbe()
|
||||||
|
val child = system.actorOf(Props(classOf[DependentChild], probe.ref))
|
||||||
|
probe.send(child, "ping")
|
||||||
|
probe.expectMsg("pong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"A DependentParent" should {
|
||||||
|
"be tested with custom props" in {
|
||||||
|
val probe = TestProbe()
|
||||||
|
val parent = TestActorRef(new DependentParent(Props[MockedChild]))
|
||||||
|
probe.send(parent, "pingit")
|
||||||
|
// test some parent state change
|
||||||
|
parent.underlyingActor.ponged should (be(true) or be(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"A GenericDependentParent" should {
|
||||||
|
"be tested with a child probe" in {
|
||||||
|
val probe = TestProbe()
|
||||||
|
//#child-maker-test
|
||||||
|
val maker = (_: ActorRefFactory) => probe.ref
|
||||||
|
val parent = system.actorOf(Props(classOf[GenericDependentParent], maker))
|
||||||
|
//#child-maker-test
|
||||||
|
probe.send(parent, "pingit")
|
||||||
|
probe.expectMsg("ping")
|
||||||
|
}
|
||||||
|
|
||||||
|
"demonstrate production version of child creator" in {
|
||||||
|
//#child-maker-prod
|
||||||
|
val maker = (f: ActorRefFactory) => f.actorOf(Props[Child])
|
||||||
|
val parent = system.actorOf(Props(classOf[GenericDependentParent], maker))
|
||||||
|
//#child-maker-prod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#test-fabricated-parent
|
||||||
|
"A fabricated parent" should {
|
||||||
|
"test its child responses" in {
|
||||||
|
val proxy = TestProbe()
|
||||||
|
val parent = system.actorOf(Props(new Actor {
|
||||||
|
val child = context.actorOf(Props[Child], "child")
|
||||||
|
def receive = {
|
||||||
|
case x if sender == child => proxy.ref forward x
|
||||||
|
case x => child forward x
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
proxy.send(parent, "ping")
|
||||||
|
proxy.expectMsg("pong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#test-fabricated-parent
|
||||||
|
}
|
||||||
|
|
@ -526,6 +526,63 @@ do not react to each other's deadlines or to the deadline set in an enclosing
|
||||||
|
|
||||||
Here, the ``expectMsg`` call will use the default timeout.
|
Here, the ``expectMsg`` call will use the default timeout.
|
||||||
|
|
||||||
|
Testing parent-child relationships
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
The parent of an actor is always the actor that created it. At times this leads to
|
||||||
|
a coupling between the two that may not be straightforward to test.
|
||||||
|
Broadly, there are three approaches to improve testability of parent-child
|
||||||
|
relationships:
|
||||||
|
|
||||||
|
1. when creating a child, pass an explicit reference to its parent
|
||||||
|
2. when creating a parent, tell the parent how to create its child
|
||||||
|
3. create a fabricated parent when testing
|
||||||
|
|
||||||
|
For example, the structure of the code you want to test may follow this pattern:
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildSpec.scala#test-example
|
||||||
|
|
||||||
|
Using dependency-injection
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The first option is to avoid use of the :meth:`context.parent` function and create
|
||||||
|
a child with a custom parent by passing an explicit reference to its parent instead.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildSpec.scala#test-dependentchild
|
||||||
|
|
||||||
|
Alternatively, you can tell the parent how to create its child. There are two ways
|
||||||
|
to do this: by giving it a :class:`Props` object or by giving it a function which takes care of creating the child actor:
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildSpec.scala#test-dependentparent
|
||||||
|
|
||||||
|
Creating the :class:`Props` is straightforward and the function may look like this in your test code:
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildSpec.scala#child-maker-test
|
||||||
|
|
||||||
|
And like this in your application code:
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildSpec.scala#child-maker-prod
|
||||||
|
|
||||||
|
Using a fabricated parent
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If you prefer to avoid modifying the parent or child constructor you can
|
||||||
|
create a fabricated parent in your test. This, however, does not enable you to test
|
||||||
|
the parent actor in isolation.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildSpec.scala#test-fabricated-parent
|
||||||
|
|
||||||
|
Which of these methods is the best depends on what is most important to test. The
|
||||||
|
most generic option is to create the parent actor by passing it a function that is
|
||||||
|
responsible for the Actor creation, but the fabricated parent is often sufficient.
|
||||||
|
|
||||||
|
|
||||||
|
.. includecode:: code/docs/testkit/ParentChildSpec.scala#test-fabricated-parent
|
||||||
|
|
||||||
|
Which of these methods is the best depends on what is most important to test. The
|
||||||
|
most generic option is to create the parent actor by passing it the partial function,
|
||||||
|
but the fabricated parent is often sufficient.
|
||||||
|
|
||||||
.. _Scala-CallingThreadDispatcher:
|
.. _Scala-CallingThreadDispatcher:
|
||||||
|
|
||||||
CallingThreadDispatcher
|
CallingThreadDispatcher
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue