Merge pull request #16192 from ktoso/doc-push-over-finish-line-actor-child-testing-ktoso
=doc #13043 Adding section on testing parent-child relationships
This commit is contained in:
commit
7dfeda0824
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.
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
CallingThreadDispatcher
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue