add JavaTestKit, see #1952
- it’s a completely new-written thing in pure Java, so that “protected” modifiers actually work and no ghost errors appear wrt. inheriting from PartialFunction or similar - it also features integration with the EventFilter - all closure-based constructs are modeled as inner classes of the JavaTestKit, where the user needs to override a single method which will then be executed
This commit is contained in:
parent
be74eb835b
commit
d7bed79730
15 changed files with 964 additions and 365 deletions
|
|
@ -586,6 +586,7 @@ object Logging {
|
||||||
/** Null Object used for errors without cause Throwable */
|
/** Null Object used for errors without cause Throwable */
|
||||||
object NoCause extends NoStackTrace
|
object NoCause extends NoStackTrace
|
||||||
}
|
}
|
||||||
|
def noCause = Error.NoCause
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For WARNING Logging
|
* For WARNING Logging
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,9 @@ trait Creator[T] {
|
||||||
}
|
}
|
||||||
|
|
||||||
object PurePartialFunction {
|
object PurePartialFunction {
|
||||||
case object NoMatch extends RuntimeException with NoStackTrace
|
sealed abstract class NoMatchException extends RuntimeException with NoStackTrace
|
||||||
|
case object NoMatch extends NoMatchException
|
||||||
|
final def noMatch(): RuntimeException = NoMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -86,7 +88,16 @@ abstract class PurePartialFunction[A, B] extends scala.runtime.AbstractFunction1
|
||||||
|
|
||||||
final def isDefinedAt(x: A): Boolean = try { apply(x, true); true } catch { case NoMatch ⇒ false }
|
final def isDefinedAt(x: A): Boolean = try { apply(x, true); true } catch { case NoMatch ⇒ false }
|
||||||
final def apply(x: A): B = try apply(x, false) catch { case NoMatch ⇒ throw new MatchError }
|
final def apply(x: A): B = try apply(x, false) catch { case NoMatch ⇒ throw new MatchError }
|
||||||
final def noMatch(): RuntimeException = NoMatch
|
}
|
||||||
|
|
||||||
|
abstract class CachingPartialFunction[A, B <: AnyRef] extends scala.runtime.AbstractFunction1[A, B] with PartialFunction[A, B] {
|
||||||
|
import PurePartialFunction._
|
||||||
|
|
||||||
|
def `match`(x: A): B
|
||||||
|
|
||||||
|
var cache: B = _
|
||||||
|
final def isDefinedAt(x: A): Boolean = try { cache = `match`(x); true } catch { case NoMatch ⇒ cache = null.asInstanceOf[B]; false }
|
||||||
|
final def apply(x: A): B = cache
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -164,4 +175,6 @@ object Util {
|
||||||
def manifest[T](clazz: Class[T]): Manifest[T] = Manifest.classType(clazz)
|
def manifest[T](clazz: Class[T]): Manifest[T] = Manifest.classType(clazz)
|
||||||
|
|
||||||
def arrayToSeq[T](arr: Array[T]): Seq[T] = arr.toSeq
|
def arrayToSeq[T](arr: Array[T]): Seq[T] = arr.toSeq
|
||||||
|
|
||||||
|
def arrayToSeq(classes: Array[Class[_]]): Seq[Class[_]] = classes.toSeq
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,31 @@
|
||||||
/*
|
/**
|
||||||
*
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||||
*/
|
*/
|
||||||
package docs.testkit;
|
package docs.testkit;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.typesafe.config.ConfigFactory;
|
||||||
|
import com.typesafe.config.Config;
|
||||||
|
|
||||||
|
import akka.actor.ActorKilledException;
|
||||||
|
import akka.actor.ActorRef;
|
||||||
import akka.actor.ActorSystem;
|
import akka.actor.ActorSystem;
|
||||||
|
import akka.actor.Kill;
|
||||||
import akka.actor.Props;
|
import akka.actor.Props;
|
||||||
import akka.actor.UntypedActor;
|
import akka.actor.UntypedActor;
|
||||||
|
import akka.actor.UntypedActorFactory;
|
||||||
import akka.dispatch.Await;
|
import akka.dispatch.Await;
|
||||||
import akka.dispatch.Future;
|
import akka.dispatch.Future;
|
||||||
import akka.japi.JAPI;
|
import akka.testkit.CallingThreadDispatcher;
|
||||||
import akka.japi.PurePartialFunction;
|
import akka.testkit.TestActor;
|
||||||
|
import akka.testkit.TestActor.AutoPilot;
|
||||||
import akka.testkit.TestActorRef;
|
import akka.testkit.TestActorRef;
|
||||||
import akka.testkit.TestKit;
|
import akka.testkit.JavaTestKit;
|
||||||
import akka.util.Duration;
|
import akka.util.Duration;
|
||||||
|
|
||||||
public class TestKitDocTest {
|
public class TestKitDocTest {
|
||||||
|
|
@ -37,8 +46,11 @@ public class TestKitDocTest {
|
||||||
|
|
||||||
private static ActorSystem system;
|
private static ActorSystem system;
|
||||||
|
|
||||||
public TestKitDocTest() {
|
@BeforeClass
|
||||||
system = ActorSystem.create();
|
public static void setup() {
|
||||||
|
final Config config = ConfigFactory.parseString(
|
||||||
|
"akka.event-handlers = [akka.testkit.TestEventListener]");
|
||||||
|
system = ActorSystem.create("demoSystem", config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
|
|
@ -50,82 +62,338 @@ public class TestKitDocTest {
|
||||||
@Test
|
@Test
|
||||||
public void demonstrateTestActorRef() {
|
public void demonstrateTestActorRef() {
|
||||||
final Props props = new Props(MyActor.class);
|
final Props props = new Props(MyActor.class);
|
||||||
final TestActorRef<MyActor> ref = TestActorRef.apply(props, system);
|
final TestActorRef<MyActor> ref = TestActorRef.create(system, props, "testA");
|
||||||
final MyActor actor = ref.underlyingActor();
|
final MyActor actor = ref.underlyingActor();
|
||||||
assertTrue(actor.testMe());
|
assertTrue(actor.testMe());
|
||||||
}
|
}
|
||||||
//#test-actor-ref
|
//#test-actor-ref
|
||||||
|
|
||||||
//#test-behavior
|
|
||||||
@Test
|
@Test
|
||||||
public void demonstrateAsk() throws Exception {
|
public void demonstrateAsk() throws Exception {
|
||||||
|
//#test-behavior
|
||||||
final Props props = new Props(MyActor.class);
|
final Props props = new Props(MyActor.class);
|
||||||
final TestActorRef<MyActor> ref = TestActorRef.apply(props, system);
|
final TestActorRef<MyActor> ref = TestActorRef.create(system, props, "testB");
|
||||||
final Future<Object> future = akka.pattern.Patterns.ask(ref, "say42", 3000);
|
final Future<Object> future = akka.pattern.Patterns.ask(ref, "say42", 3000);
|
||||||
assertTrue(future.isCompleted());
|
assertTrue(future.isCompleted());
|
||||||
assertEquals(42, Await.result(future, Duration.Zero()));
|
assertEquals(42, Await.result(future, Duration.Zero()));
|
||||||
|
//#test-behavior
|
||||||
}
|
}
|
||||||
//#test-behavior
|
|
||||||
|
|
||||||
//#test-expecting-exceptions
|
|
||||||
@Test
|
@Test
|
||||||
public void demonstrateExceptions() {
|
public void demonstrateExceptions() {
|
||||||
|
//#test-expecting-exceptions
|
||||||
final Props props = new Props(MyActor.class);
|
final Props props = new Props(MyActor.class);
|
||||||
final TestActorRef<MyActor> ref = TestActorRef.apply(props, system);
|
final TestActorRef<MyActor> ref = TestActorRef.create(system, props, "myActor");
|
||||||
try {
|
try {
|
||||||
ref.receive(new Exception("expected"));
|
ref.receive(new Exception("expected"));
|
||||||
fail("expected an exception to be thrown");
|
fail("expected an exception to be thrown");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
assertEquals("expected", e.getMessage());
|
assertEquals("expected", e.getMessage());
|
||||||
}
|
}
|
||||||
|
//#test-expecting-exceptions
|
||||||
}
|
}
|
||||||
//#test-expecting-exceptions
|
|
||||||
|
|
||||||
//#test-within
|
|
||||||
@Test
|
@Test
|
||||||
public void demonstrateWithin() {
|
public void demonstrateWithin() {
|
||||||
new TestKit(system) {{
|
//#test-within
|
||||||
testActor().tell(42);
|
new JavaTestKit(system) {{
|
||||||
new Within(Duration.parse("1 second")) {
|
getRef().tell(42);
|
||||||
|
new Within(Duration.Zero(), Duration.parse("1 second")) {
|
||||||
// do not put code outside this method, will run afterwards
|
// do not put code outside this method, will run afterwards
|
||||||
public void run() {
|
public void run() {
|
||||||
assertEquals((Integer) 42, expectMsgClass(Integer.class));
|
assertEquals((Integer) 42, expectMsgClass(Integer.class));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}};
|
}};
|
||||||
|
//#test-within
|
||||||
}
|
}
|
||||||
//#test-within
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void demonstrateExpectMsgPF() {
|
public void demonstrateExpectMsg() {
|
||||||
new TestKit(system) {{
|
//#test-expectmsg
|
||||||
testActor().tell(42);
|
new JavaTestKit(system) {{
|
||||||
//#test-expect-pf
|
getRef().tell(42);
|
||||||
final String out = expectMsgPF(Duration.parse("1 second"), "fourty-two",
|
final String out = new ExpectMsg<String>("match hint") {
|
||||||
new PurePartialFunction<Object, String>() {
|
// do not put code outside this method, will run afterwards
|
||||||
public String apply(Object in, boolean isCheck) {
|
protected String match(Object in) {
|
||||||
if (Integer.valueOf(42).equals(in)) {
|
if (in instanceof Integer) {
|
||||||
return "match";
|
return "match";
|
||||||
} else {
|
} else {
|
||||||
throw noMatch();
|
throw noMatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.get(); // this extracts the received message
|
||||||
);
|
|
||||||
assertEquals("match", out);
|
assertEquals("match", out);
|
||||||
//#test-expect-pf
|
}};
|
||||||
testActor().tell("world");
|
//#test-expectmsg
|
||||||
//#test-expect-anyof
|
}
|
||||||
final String any = expectMsgAnyOf(remaining(), JAPI.seq("hello", "world"));
|
|
||||||
//#test-expect-anyof
|
@Test
|
||||||
assertEquals("world", any);
|
public void demonstrateReceiveWhile() {
|
||||||
testActor().tell("world");
|
//#test-receivewhile
|
||||||
//#test-expect-anyclassof
|
new JavaTestKit(system) {{
|
||||||
@SuppressWarnings("unchecked")
|
getRef().tell(42);
|
||||||
final String anyClass = expectMsgAnyClassOf(remaining(), JAPI.<Class<? extends String>>seq(String.class));
|
getRef().tell(43);
|
||||||
//#test-expect-anyclassof
|
getRef().tell("hello");
|
||||||
assertEquals("world", any);
|
final String[] out =
|
||||||
|
new ReceiveWhile<String>(String.class, duration("1 second")) {
|
||||||
|
// do not put code outside this method, will run afterwards
|
||||||
|
protected String match(Object in) {
|
||||||
|
if (in instanceof Integer) {
|
||||||
|
return in.toString();
|
||||||
|
} else {
|
||||||
|
throw noMatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.get(); // this extracts the received messages
|
||||||
|
assertArrayEquals(new String[] {"42", "43"}, out);
|
||||||
|
expectMsgEquals("hello");
|
||||||
|
}};
|
||||||
|
//#test-receivewhile
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
//#test-receivewhile-full
|
||||||
|
new ReceiveWhile<String>( // type of array to be created must match ...
|
||||||
|
String.class, // ... this class which is needed to that end
|
||||||
|
duration("100 millis"), // maximum collect time
|
||||||
|
duration("50 millis"), // maximum time between messages
|
||||||
|
12 // maximum number of messages to collect
|
||||||
|
) {
|
||||||
|
//#match-elided
|
||||||
|
protected String match(Object in) {
|
||||||
|
throw noMatch();
|
||||||
|
}
|
||||||
|
//#match-elided
|
||||||
|
};
|
||||||
|
//#test-receivewhile-full
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void demonstrateAwaitCond() {
|
||||||
|
//#test-awaitCond
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
getRef().tell(42);
|
||||||
|
new AwaitCond(
|
||||||
|
duration("1 second"), // maximum wait time
|
||||||
|
duration("100 millis") // interval at which to check the condition
|
||||||
|
) {
|
||||||
|
// do not put code outside this method, will run afterwards
|
||||||
|
protected boolean cond() {
|
||||||
|
// typically used to wait for something to start up
|
||||||
|
return msgAvailable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}};
|
||||||
|
//#test-awaitCond
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked") // due to generic varargs
|
||||||
|
public void demonstrateExpect() {
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
getRef().tell("hello");
|
||||||
|
getRef().tell("hello");
|
||||||
|
getRef().tell("hello");
|
||||||
|
getRef().tell("world");
|
||||||
|
getRef().tell(42);
|
||||||
|
getRef().tell(42);
|
||||||
|
//#test-expect
|
||||||
|
final String hello = expectMsgEquals("hello");
|
||||||
|
final Object any = expectMsgAnyOf("hello", "world");
|
||||||
|
final Object[] all = expectMsgAllOf("hello", "world");
|
||||||
|
final int i = expectMsgClass(Integer.class);
|
||||||
|
final Number j = expectMsgAnyClassOf(Integer.class, Long.class);
|
||||||
|
expectNoMsg();
|
||||||
|
//#test-expect
|
||||||
|
assertEquals("hello", hello);
|
||||||
|
assertEquals("hello", any);
|
||||||
|
assertEquals(42, i);
|
||||||
|
assertEquals(42, j);
|
||||||
|
assertArrayEquals(new String[] {"hello", "world"}, all);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void demonstrateIgnoreMsg() {
|
||||||
|
//#test-ignoreMsg
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
// ignore all Strings
|
||||||
|
new IgnoreMsg() {
|
||||||
|
protected boolean ignore(Object msg) {
|
||||||
|
return msg instanceof String;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getRef().tell("hello");
|
||||||
|
getRef().tell(42);
|
||||||
|
expectMsgEquals(42);
|
||||||
|
// remove message filter
|
||||||
|
ignoreNoMsg();
|
||||||
|
getRef().tell("hello");
|
||||||
|
expectMsgEquals("hello");
|
||||||
|
}};
|
||||||
|
//#test-ignoreMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void demonstrateDilated() {
|
||||||
|
//#duration-dilation
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
final Duration original = duration("1 second");
|
||||||
|
final Duration stretched = dilated(original);
|
||||||
|
assertTrue("dilated", stretched.gteq(original));
|
||||||
|
}};
|
||||||
|
//#duration-dilation
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void demonstrateProbe() {
|
||||||
|
//#test-probe
|
||||||
|
// simple actor which just forwards messages
|
||||||
|
class Forwarder extends UntypedActor {
|
||||||
|
final ActorRef target;
|
||||||
|
public Forwarder(ActorRef target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
public void onReceive(Object msg) {
|
||||||
|
target.forward(msg, getContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
// create a test probe
|
||||||
|
final JavaTestKit probe = new JavaTestKit(system);
|
||||||
|
|
||||||
|
// create a forwarder, injecting the probe’s testActor
|
||||||
|
final Props props = new Props(new UntypedActorFactory() {
|
||||||
|
private static final long serialVersionUID = 8927158735963950216L;
|
||||||
|
public UntypedActor create() {
|
||||||
|
return new Forwarder(probe.getRef());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final ActorRef forwarder = system.actorOf(props, "forwarder");
|
||||||
|
|
||||||
|
// verify correct forwarding
|
||||||
|
forwarder.tell(42, getRef());
|
||||||
|
probe.expectMsgEquals(42);
|
||||||
|
assertEquals(getRef(), probe.getLastSender());
|
||||||
|
}};
|
||||||
|
//#test-probe
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void demonstrateSpecialProbe() {
|
||||||
|
//#test-special-probe
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
class MyProbe extends JavaTestKit {
|
||||||
|
public MyProbe() {
|
||||||
|
super(system);
|
||||||
|
}
|
||||||
|
public void assertHello() {
|
||||||
|
expectMsgEquals("hello");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final MyProbe probe = new MyProbe();
|
||||||
|
probe.getRef().tell("hello");
|
||||||
|
probe.assertHello();
|
||||||
|
}};
|
||||||
|
//#test-special-probe
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void demonstrateReply() {
|
||||||
|
//#test-probe-reply
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
final JavaTestKit probe = new JavaTestKit(system);
|
||||||
|
probe.getRef().tell("hello", getRef());
|
||||||
|
probe.expectMsgEquals("hello");
|
||||||
|
probe.reply("world");
|
||||||
|
expectMsgEquals("world");
|
||||||
|
assertEquals(probe.getRef(), getLastSender());
|
||||||
|
}};
|
||||||
|
//#test-probe-reply
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void demonstrateForward() {
|
||||||
|
//#test-probe-forward
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
final JavaTestKit probe = new JavaTestKit(system);
|
||||||
|
probe.getRef().tell("hello", getRef());
|
||||||
|
probe.expectMsgEquals("hello");
|
||||||
|
probe.forward(getRef());
|
||||||
|
expectMsgEquals("hello");
|
||||||
|
assertEquals(getRef(), getLastSender());
|
||||||
|
}};
|
||||||
|
//#test-probe-forward
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void demonstrateWithinProbe() {
|
||||||
|
try {
|
||||||
|
//#test-within-probe
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
final JavaTestKit probe = new JavaTestKit(system);
|
||||||
|
new Within(duration("1 second")) {
|
||||||
|
public void run() {
|
||||||
|
probe.expectMsgEquals("hello");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}};
|
||||||
|
//#test-within-probe
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
// expected to fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void demonstrateAutoPilot() {
|
||||||
|
//#test-auto-pilot
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
final JavaTestKit probe = new JavaTestKit(system);
|
||||||
|
// install auto-pilot
|
||||||
|
probe.setAutoPilot(new TestActor.AutoPilot() {
|
||||||
|
public AutoPilot run(ActorRef sender, Object msg) {
|
||||||
|
sender.tell(msg);
|
||||||
|
return noAutoPilot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// first one is replied to directly ...
|
||||||
|
probe.getRef().tell("hello", getRef());
|
||||||
|
expectMsgEquals("hello");
|
||||||
|
// ... but then the auto-pilot switched itself off
|
||||||
|
probe.getRef().tell("world", getRef());
|
||||||
|
expectNoMsg();
|
||||||
|
}};
|
||||||
|
//#test-auto-pilot
|
||||||
|
}
|
||||||
|
|
||||||
|
// only compilation
|
||||||
|
public void demonstrateCTD() {
|
||||||
|
//#calling-thread-dispatcher
|
||||||
|
system.actorOf(
|
||||||
|
new Props(MyActor.class)
|
||||||
|
.withDispatcher(CallingThreadDispatcher.Id()));
|
||||||
|
//#calling-thread-dispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void demonstrateEventFilter() {
|
||||||
|
//#test-event-filter
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
assertEquals("demoSystem", system.name());
|
||||||
|
final ActorRef victim = system.actorOf(Props.empty(), "victim");
|
||||||
|
|
||||||
|
final int result = new EventFilter<Integer>(ActorKilledException.class) {
|
||||||
|
protected Integer run() {
|
||||||
|
victim.tell(Kill.getInstance());
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}.from("akka://demoSystem/user/victim").occurrences(1).exec();
|
||||||
|
|
||||||
|
assertEquals(42, result);
|
||||||
|
}};
|
||||||
|
//#test-event-filter
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
95
akka-docs/java/code/docs/testkit/TestKitSampleTest.java
Normal file
95
akka-docs/java/code/docs/testkit/TestKitSampleTest.java
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
package docs.testkit;
|
||||||
|
|
||||||
|
//#fullsample
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import akka.actor.ActorRef;
|
||||||
|
import akka.actor.ActorSystem;
|
||||||
|
import akka.actor.Props;
|
||||||
|
import akka.actor.UntypedActor;
|
||||||
|
import akka.testkit.JavaTestKit;
|
||||||
|
import akka.util.Duration;
|
||||||
|
|
||||||
|
public class TestKitSampleTest {
|
||||||
|
|
||||||
|
public static class SomeActor extends UntypedActor {
|
||||||
|
ActorRef target = null;
|
||||||
|
|
||||||
|
public void onReceive(Object msg) {
|
||||||
|
|
||||||
|
if (msg.equals("hello")) {
|
||||||
|
getSender().tell("world");
|
||||||
|
if (target != null) target.forward(msg, getContext());
|
||||||
|
|
||||||
|
} else if (msg instanceof ActorRef) {
|
||||||
|
target = (ActorRef) msg;
|
||||||
|
getSender().tell("done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ActorSystem system;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setup() {
|
||||||
|
system = ActorSystem.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void teardown() {
|
||||||
|
system.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIt() {
|
||||||
|
/*
|
||||||
|
* Wrap the whole test procedure within a testkit constructor
|
||||||
|
* if you want to receive actor replies or use Within(), etc.
|
||||||
|
*/
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
final Props props = new Props(SomeActor.class);
|
||||||
|
final ActorRef subject = system.actorOf(props);
|
||||||
|
|
||||||
|
// can also use JavaTestKit “from the outside”
|
||||||
|
final JavaTestKit probe = new JavaTestKit(system);
|
||||||
|
// “inject” the probe by passing it to the test subject
|
||||||
|
// like a real resource would be passed in production
|
||||||
|
subject.tell(probe.getRef(), getRef());
|
||||||
|
// await the correct response
|
||||||
|
expectMsgEquals(duration("1 second"), "done");
|
||||||
|
|
||||||
|
// the run() method needs to finish within 3 seconds
|
||||||
|
new Within(duration("3 seconds")) {
|
||||||
|
protected void run() {
|
||||||
|
|
||||||
|
subject.tell("hello", getRef());
|
||||||
|
|
||||||
|
// This is a demo: would normally use expectMsgEquals().
|
||||||
|
// Wait time is bounded by 3-second deadline above.
|
||||||
|
new AwaitCond() {
|
||||||
|
protected boolean cond() {
|
||||||
|
return probe.msgAvailable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// response must have been enqueued to us before probe
|
||||||
|
expectMsgEquals(Duration.Zero(), "world");
|
||||||
|
// check that the probe we injected earlier got the msg
|
||||||
|
probe.expectMsgEquals(Duration.Zero(), "hello");
|
||||||
|
Assert.assertEquals(getRef(), probe.getLastSender());
|
||||||
|
|
||||||
|
// Will wait for the rest of the 3 seconds
|
||||||
|
expectNoMsg();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//#fullsample
|
||||||
|
|
@ -92,7 +92,7 @@ There are 4 different types of message dispatchers:
|
||||||
* CallingThreadDispatcher
|
* CallingThreadDispatcher
|
||||||
|
|
||||||
- This dispatcher runs invocations on the current thread only. This dispatcher does not create any new threads,
|
- This dispatcher runs invocations on the current thread only. This dispatcher does not create any new threads,
|
||||||
but it can be used from different threads concurrently for the same actor. See :ref:`TestCallingThreadDispatcherRef`
|
but it can be used from different threads concurrently for the same actor. See :ref:`Java-CallingThreadDispatcher`
|
||||||
for details and restrictions.
|
for details and restrictions.
|
||||||
|
|
||||||
- Sharability: Unlimited
|
- Sharability: Unlimited
|
||||||
|
|
|
||||||
|
|
@ -141,170 +141,152 @@ Feel free to experiment with the possibilities, and if you find useful
|
||||||
patterns, don't hesitate to let the Akka forums know about them! Who knows,
|
patterns, don't hesitate to let the Akka forums know about them! Who knows,
|
||||||
common operations might even be worked into nice DSLs.
|
common operations might even be worked into nice DSLs.
|
||||||
|
|
||||||
Integration Testing with :class:`TestKit`
|
Integration Testing with :class:`JavaTestKit`
|
||||||
=========================================
|
=============================================
|
||||||
|
|
||||||
When you are reasonably sure that your actor's business logic is correct, the
|
When you are reasonably sure that your actor's business logic is correct, the
|
||||||
next step is verifying that it works correctly within its intended environment
|
next step is verifying that it works correctly within its intended environment.
|
||||||
(if the individual actors are simple enough, possibly because they use the
|
The definition of the environment depends of course very much on the problem at
|
||||||
:mod:`FSM` module, this might also be the first step). The definition of the
|
hand and the level at which you intend to test, ranging for
|
||||||
environment depends of course very much on the problem at hand and the level at
|
functional/integration tests to full system tests. The minimal setup consists
|
||||||
which you intend to test, ranging for functional/integration tests to full
|
of the test procedure, which provides the desired stimuli, the actor under
|
||||||
system tests. The minimal setup consists of the test procedure, which provides
|
test, and an actor receiving replies. Bigger systems replace the actor under
|
||||||
the desired stimuli, the actor under test, and an actor receiving replies.
|
test with a network of actors, apply stimuli at varying injection points and
|
||||||
Bigger systems replace the actor under test with a network of actors, apply
|
arrange results to be sent from different emission points, but the basic
|
||||||
stimuli at varying injection points and arrange results to be sent from
|
principle stays the same in that a single procedure drives the test.
|
||||||
different emission points, but the basic principle stays the same in that a
|
|
||||||
single procedure drives the test.
|
|
||||||
|
|
||||||
The :class:`TestKit` class contains a collection of tools which makes this
|
The :class:`JavaTestKit` class contains a collection of tools which makes this
|
||||||
common task easy.
|
common task easy.
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/PlainWordTest.java#plain-spec
|
.. includecode:: code/docs/testkit/TestKitSampleTest.java#fullsample
|
||||||
|
|
||||||
The :class:`TestKit` contains an actor named :obj:`testActor` which is the
|
The :class:`JavaTestKit` contains an actor named :obj:`testActor` which is the
|
||||||
entry point for messages to be examined with the various ``expectMsg...``
|
entry point for messages to be examined with the various ``expectMsg...``
|
||||||
assertions detailed below. When mixing in the trait ``ImplicitSender`` this
|
assertions detailed below. The test actor’s reference is obtained using the
|
||||||
test actor is implicitly used as sender reference when dispatching messages
|
:meth:`getRef()` method as demonstrated above. The :obj:`testActor` may also
|
||||||
from the test procedure. The :obj:`testActor` may also be passed to
|
be passed to other actors as usual, usually subscribing it as notification
|
||||||
other actors as usual, usually subscribing it as notification listener. There
|
listener. There is a whole set of examination methods, e.g. receiving all
|
||||||
is a whole set of examination methods, e.g. receiving all consecutive messages
|
consecutive messages matching certain criteria, receiving a whole sequence of
|
||||||
matching certain criteria, receiving a whole sequence of fixed messages or
|
fixed messages or classes, receiving nothing for some time, etc.
|
||||||
classes, receiving nothing for some time, etc.
|
|
||||||
|
|
||||||
The ActorSystem passed in to the constructor of TestKit is accessible via the
|
The ActorSystem passed in to the constructor of JavaTestKit is accessible via the
|
||||||
:meth:`system()` method. Remember to shut down the actor system after the test
|
:meth:`getSystem()` method.
|
||||||
is finished (also in case of failure) so that all actors—including the test
|
|
||||||
actor—are stopped.
|
.. note::
|
||||||
|
|
||||||
|
Remember to shut down the actor system after the test is finished (also in
|
||||||
|
case of failure) so that all actors—including the test actor—are stopped.
|
||||||
|
|
||||||
Built-In Assertions
|
Built-In Assertions
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The above mentioned :meth:`expectMsg` is not the only method for formulating
|
The above mentioned :meth:`expectMsgEquals` is not the only method for
|
||||||
assertions concerning received messages. Here is the full list:
|
formulating assertions concerning received messages, the full set is this:
|
||||||
|
|
||||||
* :meth:`<T> T expectMsg(Duration d, T msg): T`
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-expect
|
||||||
|
|
||||||
|
In these examples, the maximum durations you will find mentioned below are left
|
||||||
|
out, in which case they use the default value from configuration item
|
||||||
|
``akka.test.single-expect-default`` which itself defaults to 3 seconds (or they
|
||||||
|
obey the innermost enclosing :class:`Within` as detailed :ref:`below
|
||||||
|
<JavaTestKit.within>`). The full signatures are:
|
||||||
|
|
||||||
|
* :meth:`public <T> T expectMsgEquals(Duration max, T msg)`
|
||||||
|
|
||||||
The given message object must be received within the specified time; the
|
The given message object must be received within the specified time; the
|
||||||
object will be returned.
|
object will be returned.
|
||||||
|
|
||||||
* :meth:`<T> T expectMsgPF(Duration d, PartialFunction<Object, T> pf)`
|
* :meth:`public Object expectMsgAnyOf(Duration max, Object... msg)`
|
||||||
|
|
||||||
Within the given time period, a message must be received and the given
|
An object must be received within the given time, and it must be equal
|
||||||
partial function must be defined for that message; the result from applying
|
(compared with ``equals()``) to at least one of the passed reference
|
||||||
the partial function to the received message is returned.
|
objects; the received object will be returned.
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-expect-pf
|
* :meth:`public Object[] expectMsgAllOf(Duration max, Object... msg)`
|
||||||
|
|
||||||
* :meth:`<T> T expectMsgClass(Duration d, Class<T> c)`
|
|
||||||
|
|
||||||
An object which is an instance of the given :class:`Class` must be received
|
|
||||||
within the allotted time frame; the object will be returned. Note that this
|
|
||||||
does a conformance check; if you need the class to be equal, have a look at
|
|
||||||
:meth:`expectMsgAllClassOf` with a single given class argument.
|
|
||||||
|
|
||||||
* :meth:`<T> T expectMsgAnyOf(Duration d, Seq<T> obj)`
|
|
||||||
|
|
||||||
An object must be received within the given time, and it must be equal (
|
|
||||||
compared with ``equals()``) to at least one of the passed reference objects; the
|
|
||||||
received object will be returned.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-expect-anyof
|
|
||||||
|
|
||||||
* :meth:`<T> T expectMsgAnyClassOf(Duration d, Seq<Class<? extends T>> classes)`
|
|
||||||
|
|
||||||
An object must be received within the given time, and it must be an
|
|
||||||
instance of at least one of the supplied :class:`Class` objects; the
|
|
||||||
received object will be returned.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-expect-anyclassof
|
|
||||||
|
|
||||||
* :meth:`expectMsgAllOf[T](d: Duration, obj: T*): Seq[T]`
|
|
||||||
|
|
||||||
A number of objects matching the size of the supplied object array must be
|
A number of objects matching the size of the supplied object array must be
|
||||||
received within the given time, and for each of the given objects there
|
received within the given time, and for each of the given objects there
|
||||||
must exist at least one among the received ones which equals (compared with
|
must exist at least one among the received ones which equals it (compared
|
||||||
``==``) it. The full sequence of received objects is returned.
|
with ``equals()``). The full sequence of received objects is returned in
|
||||||
|
the order received.
|
||||||
|
|
||||||
* :meth:`expectMsgAllClassOf[T](d: Duration, c: Class[_ <: T]*): Seq[T]`
|
* :meth:`public <T> T expectMsgClass(Duration max, Class<T> c)`
|
||||||
|
|
||||||
A number of objects matching the size of the supplied :class:`Class` array
|
An object which is an instance of the given :class:`Class` must be received
|
||||||
must be received within the given time, and for each of the given classes
|
within the allotted time frame; the object will be returned. Note that this
|
||||||
there must exist at least one among the received objects whose class equals
|
does a conformance check, if you need the class to be equal you need to
|
||||||
(compared with ``==``) it (this is *not* a conformance check). The full
|
verify that afterwards.
|
||||||
sequence of received objects is returned.
|
|
||||||
|
|
||||||
* :meth:`expectMsgAllConformingOf[T](d: Duration, c: Class[_ <: T]*): Seq[T]`
|
* :meth:`public <T> T expectMsgAnyClassOf(Duration max, Class<? extends T>... c)`
|
||||||
|
|
||||||
A number of objects matching the size of the supplied :class:`Class` array
|
An object must be received within the given time, and it must be an
|
||||||
must be received within the given time, and for each of the given classes
|
instance of at least one of the supplied :class:`Class` objects; the
|
||||||
there must exist at least one among the received objects which is an
|
received object will be returned. Note that this does a conformance check,
|
||||||
instance of this class. The full sequence of received objects is returned.
|
if you need the class to be equal you need to verify that afterwards.
|
||||||
|
|
||||||
* :meth:`expectNoMsg(d: Duration)`
|
.. note::
|
||||||
|
|
||||||
|
Because of a limitation in Java’s type system it may be necessary to add
|
||||||
|
``@SuppressWarnings("unchecked")`` when using this method.
|
||||||
|
|
||||||
|
* :meth:`public void expectNoMsg(Duration max)`
|
||||||
|
|
||||||
No message must be received within the given time. This also fails if a
|
No message must be received within the given time. This also fails if a
|
||||||
message has been received before calling this method which has not been
|
message has been received before calling this method which has not been
|
||||||
removed from the queue using one of the other methods.
|
removed from the queue using one of the other methods.
|
||||||
|
|
||||||
* :meth:`receiveN(n: Int, d: Duration): Seq[AnyRef]`
|
For cases which require more refined conditions there are constructs which take
|
||||||
|
code blocks:
|
||||||
|
|
||||||
``n`` messages must be received within the given time; the received
|
* **ExpectMsg<T>**
|
||||||
messages are returned.
|
|
||||||
|
|
||||||
* :meth:`fishForMessage(max: Duration, hint: String)(pf: PartialFunction[Any, Boolean]): Any`
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-expectmsg
|
||||||
|
|
||||||
Keep receiving messages as long as the time is not used up and the partial
|
The :meth:`match(Object in)` method will be evaluated once a message has
|
||||||
function matches and returns ``false``. Returns the message received for
|
been received within the allotted time (which may be given as constructor
|
||||||
which it returned ``true`` or throws an exception, which will include the
|
argument). If it throws ``noMatch()`` (where it is sufficient to call that
|
||||||
provided hint for easier debugging.
|
method; the ``throw`` keyword is only needed in cases where the compiler
|
||||||
|
would otherwise complain about wrong return types—Java is lacking Scala’s
|
||||||
|
notion of a type which signifies “will not ever return normally”), then the
|
||||||
|
expectation fails with an :class:`AssertionError`, otherwise the matched
|
||||||
|
and possibly transformed object is stored for retrieval using the
|
||||||
|
:meth:`get()` method.
|
||||||
|
|
||||||
In addition to message reception assertions there are also methods which help
|
* **ReceiveWhile<T>**
|
||||||
with message flows:
|
|
||||||
|
|
||||||
* :meth:`receiveOne(d: Duration): AnyRef`
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-receivewhile
|
||||||
|
|
||||||
Tries to receive one message for at most the given time interval and
|
This construct works like ExpectMsg, but it continually collects messages
|
||||||
returns ``null`` in case of failure. If the given Duration is zero, the
|
as long as they match the criteria, and it does not fail when a
|
||||||
call is non-blocking (polling mode).
|
non-matching one is encountered. Collecting messages also ends when the
|
||||||
|
time is up, when too much time passes between messages or when enough
|
||||||
|
messages have been received.
|
||||||
|
|
||||||
* :meth:`receiveWhile[T](max: Duration, idle: Duration, messages: Int)(pf: PartialFunction[Any, T]): Seq[T]`
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-receivewhile-full
|
||||||
|
:exclude: match-elided
|
||||||
|
|
||||||
Collect messages as long as
|
The need to specify the ``String`` result type twice results from the need
|
||||||
|
to create a correctly typed array and Java’s inability to infer the class’s
|
||||||
|
type argument.
|
||||||
|
|
||||||
* they are matching the given partial function
|
* **AwaitCond**
|
||||||
* the given time interval is not used up
|
|
||||||
* the next message is received within the idle timeout
|
|
||||||
* the number of messages has not yet reached the maximum
|
|
||||||
|
|
||||||
All collected messages are returned. The maximum duration defaults to the
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-awaitCond
|
||||||
time remaining in the innermost enclosing :ref:`within <TestKit.within>`
|
|
||||||
block and the idle duration defaults to infinity (thereby disabling the
|
|
||||||
idle timeout feature). The number of expected messages defaults to
|
|
||||||
``Int.MaxValue``, which effectively disables this limit.
|
|
||||||
|
|
||||||
* :meth:`awaitCond(p: => Boolean, max: Duration, interval: Duration)`
|
This general construct is not connected with the test kit’s message
|
||||||
|
reception, the embedded condition can compute the boolean result from
|
||||||
|
anything in scope.
|
||||||
|
|
||||||
Poll the given condition every :obj:`interval` until it returns ``true`` or
|
There are also cases where not all messages sent to the test kit are actually
|
||||||
the :obj:`max` duration is used up. The interval defaults to 100 ms and the
|
relevant to the test, but removing them would mean altering the actors under
|
||||||
maximum defaults to the time remaining in the innermost enclosing
|
test. For this purpose it is possible to ignore certain messages:
|
||||||
:ref:`within <TestKit.within>` block.
|
|
||||||
|
|
||||||
* :meth:`ignoreMsg(pf: PartialFunction[AnyRef, Boolean])`
|
* **IgnoreMsg**
|
||||||
|
|
||||||
:meth:`ignoreNoMsg`
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-ignoreMsg
|
||||||
|
|
||||||
The internal :obj:`testActor` contains a partial function for ignoring
|
Expecting Log Messages
|
||||||
messages: it will only enqueue messages which do not match the function or
|
----------------------
|
||||||
for which the function returns ``false``. This function can be set and
|
|
||||||
reset using the methods given above; each invocation replaces the previous
|
|
||||||
function, they are not composed.
|
|
||||||
|
|
||||||
This feature is useful e.g. when testing a logging system, where you want
|
|
||||||
to ignore regular messages and are only interested in your specific ones.
|
|
||||||
|
|
||||||
Expecting Exceptions
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Since an integration test does not allow to the internal processing of the
|
Since an integration test does not allow to the internal processing of the
|
||||||
participating actors, verifying expected exceptions cannot be done directly.
|
participating actors, verifying expected exceptions cannot be done directly.
|
||||||
|
|
@ -313,9 +295,23 @@ handler with the :class:`TestEventListener` and using an :class:`EventFilter`
|
||||||
allows assertions on log messages, including those which are generated by
|
allows assertions on log messages, including those which are generated by
|
||||||
exceptions:
|
exceptions:
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java#event-filter
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-event-filter
|
||||||
|
|
||||||
.. _TestKit.within:
|
If a number of occurrences is specific—as demonstrated above—then ``exec()``
|
||||||
|
will block until that number of matching messages have been received or the
|
||||||
|
timeout configured in ``akka.test.filter-leeway`` is used up (time starts
|
||||||
|
counting after the ``run()`` method returns). In case of a timeout the test
|
||||||
|
fails.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Be sure to exchange the default event handler with the
|
||||||
|
:class:`TestEventListener` in your ``application.conf`` to enable this
|
||||||
|
function::
|
||||||
|
|
||||||
|
akka.event-handlers = [akka.testkit.TestEventListener]
|
||||||
|
|
||||||
|
.. _JavaTestKit.within:
|
||||||
|
|
||||||
Timing Assertions
|
Timing Assertions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
@ -327,17 +323,13 @@ the positive or negative result must be obtained. Lower time limits need to be
|
||||||
checked external to the examination, which is facilitated by a new construct
|
checked external to the examination, which is facilitated by a new construct
|
||||||
for managing time constraints:
|
for managing time constraints:
|
||||||
|
|
||||||
.. code-block:: scala
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-within
|
||||||
|
|
||||||
within([min, ]max) {
|
The block in :meth:`Within.run()` must complete after a :ref:`Duration` which
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
The block given to :meth:`within` must complete after a :ref:`Duration` which
|
|
||||||
is between :obj:`min` and :obj:`max`, where the former defaults to zero. The
|
is between :obj:`min` and :obj:`max`, where the former defaults to zero. The
|
||||||
deadline calculated by adding the :obj:`max` parameter to the block's start
|
deadline calculated by adding the :obj:`max` parameter to the block's start
|
||||||
time is implicitly available within the block to all examination methods, if
|
time is implicitly available within the block to all examination methods, if
|
||||||
you do not specify it, is is inherited from the innermost enclosing
|
you do not specify it, it is inherited from the innermost enclosing
|
||||||
:meth:`within` block.
|
:meth:`within` block.
|
||||||
|
|
||||||
It should be noted that if the last message-receiving assertion of the block is
|
It should be noted that if the last message-receiving assertion of the block is
|
||||||
|
|
@ -346,16 +338,10 @@ 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
|
latencies. This means that while individual contained assertions still use the
|
||||||
maximum time bound, the overall block may take arbitrarily longer in this case.
|
maximum time bound, the overall block may take arbitrarily longer in this case.
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-within
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
All times are measured using ``System.nanoTime``, meaning that they describe
|
All times are measured using ``System.nanoTime``, meaning that they describe
|
||||||
wall time, not CPU time.
|
wall time, not CPU time or system time.
|
||||||
|
|
||||||
Ray Roestenburg has written a great article on using the TestKit:
|
|
||||||
`<http://roestenburg.agilesquad.com/2011/02/unit-testing-akka-actors-with-testkit_12.html>`_.
|
|
||||||
His full example is also available :ref:`here <testkit-example>`.
|
|
||||||
|
|
||||||
Accounting for Slow Test Systems
|
Accounting for Slow Test Systems
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
@ -371,33 +357,22 @@ in ``akka.testkit`` package object to add dilated function to :class:`Duration`.
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java#duration-dilation
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#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.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/PlainWordSpec.scala#implicit-sender
|
|
||||||
|
|
||||||
Using Multiple Probe Actors
|
Using Multiple Probe Actors
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
When the actors under test are supposed to send various messages to different
|
When the actors under test are supposed to send various messages to different
|
||||||
destinations, it may be difficult distinguishing the message streams arriving
|
destinations, it may be difficult distinguishing the message streams arriving
|
||||||
at the :obj:`testActor` when using the :class:`TestKit` as a mixin. Another
|
at the :obj:`testActor` when using the :class:`JavaTestKit` as shown until now.
|
||||||
approach is to use it for creation of simple probe actors to be inserted in the
|
Another approach is to use it for creation of simple probe actors to be
|
||||||
message flows. To make this more powerful and convenient, there is a concrete
|
inserted in the message flows. The functionality is best explained using a
|
||||||
implementation called :class:`TestProbe`. The functionality is best explained
|
small example:
|
||||||
using a small example:
|
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-probe
|
||||||
:include: imports-test-probe,my-double-echo,test-probe
|
|
||||||
|
|
||||||
Here a the system under test is simulated by :class:`MyDoubleEcho`, which is
|
This simple test verifies an equally simple Forwarder actor by injecting a
|
||||||
supposed to mirror its input to two outputs. Attaching two test probes enables
|
probe as the forwarder’s target. Another example would be two actors A and B
|
||||||
verification of the (simplistic) behavior. Another example would be two actors
|
which collaborate by A sending messages to B. In order to verify this message
|
||||||
A and B which collaborate by A sending messages to B. In order to verify this
|
flow, a :class:`TestProbe` could be inserted as target of A, using the
|
||||||
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
|
forwarding capabilities or auto-pilot described below to include a real B in
|
||||||
the test setup.
|
the test setup.
|
||||||
|
|
||||||
|
|
@ -407,33 +382,28 @@ more concise and clear:
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java
|
.. includecode:: code/docs/testkit/TestKitDocTest.java
|
||||||
:include: test-special-probe
|
:include: test-special-probe
|
||||||
|
|
||||||
You have complete flexibility here in mixing and matching the :class:`TestKit`
|
You have complete flexibility here in mixing and matching the
|
||||||
facilities with your own checks and choosing an intuitive name for it. In real
|
:class:`JavaTestKit` facilities with your own checks and choosing an intuitive
|
||||||
life your code will probably be a bit more complicated than the example given
|
name for it. In real life your code will probably be a bit more complicated
|
||||||
above; just use the power!
|
than the example given above; just use the power!
|
||||||
|
|
||||||
Replying to Messages Received by Probes
|
Replying to Messages Received by Probes
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The probes keep track of the communications channel for replies, if possible,
|
The probe stores the sender of the last dequeued message (i.e. after its
|
||||||
so they can also reply:
|
``expectMsg*`` reception), which may be retrieved using the
|
||||||
|
:meth:`getLastSender()` method. This information can also implicitly be used
|
||||||
|
for having the probe reply to the last received message:
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-probe-reply
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-probe-reply
|
||||||
|
|
||||||
Forwarding Messages Received by Probes
|
Forwarding Messages Received by Probes
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Given a destination actor ``dest`` which in the nominal actor network would
|
The probe can also forward a received message (i.e. after its ``expectMsg*``
|
||||||
receive a message from actor ``source``. If you arrange for the message to be
|
reception), retaining the original sender:
|
||||||
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:
|
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-probe-forward
|
||||||
:include: test-probe-forward-actors,test-probe-forward
|
|
||||||
|
|
||||||
The ``dest`` actor will receive the same message invocation as if no test probe
|
|
||||||
had intervened.
|
|
||||||
|
|
||||||
Auto-Pilot
|
Auto-Pilot
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
@ -445,7 +415,7 @@ keep a test running and verify traces later you can also install an
|
||||||
This code can be used to forward messages, e.g. in a chain ``A --> Probe -->
|
This code can be used to forward messages, e.g. in a chain ``A --> Probe -->
|
||||||
B``, as long as a certain protocol is obeyed.
|
B``, as long as a certain protocol is obeyed.
|
||||||
|
|
||||||
.. includecode:: ../../akka-testkit/src/test/scala/akka/testkit/TestProbeSpec.scala#autopilot
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-auto-pilot
|
||||||
|
|
||||||
The :meth:`run` method must return the auto-pilot for the next message, wrapped
|
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.
|
in an :class:`Option`; setting it to :obj:`None` terminates the auto-pilot.
|
||||||
|
|
@ -455,25 +425,15 @@ Caution about Timing Assertions
|
||||||
|
|
||||||
The behavior of :meth:`within` blocks when using test probes might be perceived
|
The behavior of :meth:`within` blocks when using test probes might be perceived
|
||||||
as counter-intuitive: you need to remember that the nicely scoped deadline as
|
as counter-intuitive: you need to remember that the nicely scoped deadline as
|
||||||
described :ref:`above <TestKit.within>` is local to each probe. Hence, probes
|
described :ref:`above <JavaTestKit.within>` is local to each probe. Hence, probes
|
||||||
do not react to each other's deadlines or to the deadline set in an enclosing
|
do not react to each other's deadlines or to the deadline set in an enclosing
|
||||||
:class:`TestKit` instance::
|
:class:`JavaTestKit` instance:
|
||||||
|
|
||||||
class SomeTest extends TestKit(_system: ActorSystem) with ImplicitSender {
|
.. includecode:: code/docs/testkit/TestKitDocTest.java#test-within-probe
|
||||||
|
|
||||||
val probe = TestProbe()
|
Here, the ``expectMsgEquals`` call will use the default timeout.
|
||||||
|
|
||||||
within(100 millis) {
|
.. _Java-CallingThreadDispatcher:
|
||||||
probe.expectMsg("hallo") // Will hang forever!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
This test will hang indefinitely, because the :meth:`expectMsg` call does not
|
|
||||||
see any deadline. Currently, the only option is to use ``probe.within`` in the
|
|
||||||
above code to make it work; later versions may include lexically scoped
|
|
||||||
deadlines using implicit arguments.
|
|
||||||
|
|
||||||
.. _TestCallingThreadDispatcherRef:
|
|
||||||
|
|
||||||
CallingThreadDispatcher
|
CallingThreadDispatcher
|
||||||
=======================
|
=======================
|
||||||
|
|
@ -572,7 +532,7 @@ has to offer:
|
||||||
exception stack traces
|
exception stack traces
|
||||||
- Exclusion of certain classes of dead-lock scenarios
|
- Exclusion of certain classes of dead-lock scenarios
|
||||||
|
|
||||||
.. _actor.logging:
|
.. _actor.logging-java:
|
||||||
|
|
||||||
Tracing Actor Invocations
|
Tracing Actor Invocations
|
||||||
=========================
|
=========================
|
||||||
|
|
@ -588,24 +548,6 @@ options:
|
||||||
This is always on; in contrast to the other logging mechanisms, this logs at
|
This is always on; in contrast to the other logging mechanisms, this logs at
|
||||||
``ERROR`` level.
|
``ERROR`` level.
|
||||||
|
|
||||||
* *Logging of message invocations on certain actors*
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java#logging-receive
|
|
||||||
|
|
||||||
.
|
|
||||||
If the abovementioned setting is not given in the :ref:`configuration`, this method will
|
|
||||||
pass through the given :class:`Receive` function unmodified, meaning that
|
|
||||||
there is no runtime cost unless actually enabled.
|
|
||||||
|
|
||||||
The logging feature is coupled to this specific local mark-up because
|
|
||||||
enabling it uniformly on all actors is not usually what you need, and it
|
|
||||||
would lead to endless loops if it were applied to :class:`EventHandler`
|
|
||||||
listeners.
|
|
||||||
|
|
||||||
* *Logging of special messages*
|
* *Logging of special messages*
|
||||||
|
|
||||||
Actors handle certain special messages automatically, e.g. :obj:`Kill`,
|
Actors handle certain special messages automatically, e.g. :obj:`Kill`,
|
||||||
|
|
@ -626,74 +568,10 @@ full logging of actor activities using this configuration fragment::
|
||||||
loglevel = DEBUG
|
loglevel = DEBUG
|
||||||
actor {
|
actor {
|
||||||
debug {
|
debug {
|
||||||
receive = on
|
|
||||||
autoreceive = on
|
autoreceive = on
|
||||||
lifecycle = on
|
lifecycle = on
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Different Testing Frameworks
|
|
||||||
============================
|
|
||||||
|
|
||||||
Akka’s own test suite is written using `ScalaTest`_,
|
|
||||||
which also shines through in documentation examples. However, the TestKit and
|
|
||||||
its facilities do not depend on that framework, you can essentially use
|
|
||||||
whichever suits your development style best.
|
|
||||||
|
|
||||||
This section contains a collection of known gotchas with some other frameworks,
|
|
||||||
which is by no means exhaustive and does not imply endorsement or special
|
|
||||||
support.
|
|
||||||
|
|
||||||
When you need it to be a trait
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
If for some reason it is a problem to inherit from :class:`TestKit` due to it
|
|
||||||
being a concrete class instead of a trait, there’s :class:`TestKitBase`:
|
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestKitDocTest.java
|
|
||||||
:include: test-kit-base
|
|
||||||
:exclude: put-your-test-code-here
|
|
||||||
|
|
||||||
The ``implicit lazy val system`` must be declared exactly like that (you can of
|
|
||||||
course pass arguments to the actor system factory as needed) because trait
|
|
||||||
:class:`TestKitBase` needs the system during its construction.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Use of the trait is discouraged because of potential issues with binary
|
|
||||||
backwards compatibility in the future, use at own risk.
|
|
||||||
|
|
||||||
Specs2
|
|
||||||
------
|
|
||||||
|
|
||||||
Some `Specs2`_ users have contributed examples of how to work around some clashes which may arise:
|
|
||||||
|
|
||||||
* Mixing TestKit into :class:`org.specs2.mutable.Specification` results in a
|
|
||||||
name clash involving the ``end`` method (which is a private variable in
|
|
||||||
TestKit and an abstract method in Specification); if mixing in TestKit first,
|
|
||||||
the code may compile but might then fail at runtime. The work-around—which is
|
|
||||||
actually beneficial also for the third point—is to apply the TestKit together
|
|
||||||
with :class:`org.specs2.specification.Scope`.
|
|
||||||
* The Specification traits provide a :class:`Duration` DSL which uses partly
|
|
||||||
the same method names as :class:`akka.util.Duration`, resulting in ambiguous
|
|
||||||
implicits if ``akka.util.duration._`` is imported. There are two work-arounds:
|
|
||||||
|
|
||||||
* either use the Specification variant of Duration and supply an implicit
|
|
||||||
conversion to the Akka Duration. This conversion is not supplied with the
|
|
||||||
Akka distribution because that would mean that our JAR files would dependon
|
|
||||||
Specs2, which is not justified by this little feature.
|
|
||||||
|
|
||||||
* or mix :class:`org.specs2.time.NoTimeConversions` into the Specification.
|
|
||||||
|
|
||||||
* Specifications are by default executed concurrently, which requires some care
|
|
||||||
when writing the tests or alternatively the ``sequential`` keyword.
|
|
||||||
|
|
||||||
You can use the following two examples as guidelines:
|
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/Specs2DemoSpec.scala
|
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/Specs2DemoAcceptance.scala
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -275,4 +275,15 @@ class TestkitDocSpec extends AkkaSpec with DefaultTimeout with ImplicitSender {
|
||||||
//#test-kit-base
|
//#test-kit-base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"demonstrate within() nesting" in {
|
||||||
|
intercept[AssertionError] {
|
||||||
|
//#test-within-probe
|
||||||
|
val probe = TestProbe()
|
||||||
|
within(1 second) {
|
||||||
|
probe.expectMsg("hello")
|
||||||
|
}
|
||||||
|
//#test-within-probe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ There are 4 different types of message dispatchers:
|
||||||
* CallingThreadDispatcher
|
* CallingThreadDispatcher
|
||||||
|
|
||||||
- This dispatcher runs invocations on the current thread only. This dispatcher does not create any new threads,
|
- This dispatcher runs invocations on the current thread only. This dispatcher does not create any new threads,
|
||||||
but it can be used from different threads concurrently for the same actor. See :ref:`TestCallingThreadDispatcherRef`
|
but it can be used from different threads concurrently for the same actor. See :ref:`Scala-CallingThreadDispatcher`
|
||||||
for details and restrictions.
|
for details and restrictions.
|
||||||
|
|
||||||
- Sharability: Unlimited
|
- Sharability: Unlimited
|
||||||
|
|
|
||||||
|
|
@ -424,7 +424,7 @@ This FSM will log at DEBUG level:
|
||||||
* all state transitions
|
* all state transitions
|
||||||
|
|
||||||
Life cycle changes and special messages can be logged as described for
|
Life cycle changes and special messages can be logged as described for
|
||||||
:ref:`Actors <actor.logging>`.
|
:ref:`Actors <actor.logging-scala>`.
|
||||||
|
|
||||||
Rolling Event Log
|
Rolling Event Log
|
||||||
-----------------
|
-----------------
|
||||||
|
|
|
||||||
|
|
@ -317,8 +317,8 @@ with message flows:
|
||||||
This feature is useful e.g. when testing a logging system, where you want
|
This feature is useful e.g. when testing a logging system, where you want
|
||||||
to ignore regular messages and are only interested in your specific ones.
|
to ignore regular messages and are only interested in your specific ones.
|
||||||
|
|
||||||
Expecting Exceptions
|
Expecting Log Messages
|
||||||
--------------------
|
----------------------
|
||||||
|
|
||||||
Since an integration test does not allow to the internal processing of the
|
Since an integration test does not allow to the internal processing of the
|
||||||
participating actors, verifying expected exceptions cannot be done directly.
|
participating actors, verifying expected exceptions cannot be done directly.
|
||||||
|
|
@ -329,6 +329,20 @@ exceptions:
|
||||||
|
|
||||||
.. includecode:: code/docs/testkit/TestkitDocSpec.scala#event-filter
|
.. includecode:: code/docs/testkit/TestkitDocSpec.scala#event-filter
|
||||||
|
|
||||||
|
If a number of occurrences is specific—as demonstrated above—then ``intercept``
|
||||||
|
will block until that number of matching messages have been received or the
|
||||||
|
timeout configured in ``akka.test.filter-leeway`` is used up (time starts
|
||||||
|
counting after the passed-in block of code returns). In case of a timeout the
|
||||||
|
test fails.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Be sure to exchange the default event handler with the
|
||||||
|
:class:`TestEventListener` in your ``application.conf`` to enable this
|
||||||
|
function::
|
||||||
|
|
||||||
|
akka.event-handlers = [akka.testkit.TestEventListener]
|
||||||
|
|
||||||
.. _TestKit.within:
|
.. _TestKit.within:
|
||||||
|
|
||||||
Timing Assertions
|
Timing Assertions
|
||||||
|
|
@ -351,7 +365,7 @@ The block given to :meth:`within` must complete after a :ref:`Duration` which
|
||||||
is between :obj:`min` and :obj:`max`, where the former defaults to zero. The
|
is between :obj:`min` and :obj:`max`, where the former defaults to zero. The
|
||||||
deadline calculated by adding the :obj:`max` parameter to the block's start
|
deadline calculated by adding the :obj:`max` parameter to the block's start
|
||||||
time is implicitly available within the block to all examination methods, if
|
time is implicitly available within the block to all examination methods, if
|
||||||
you do not specify it, is is inherited from the innermost enclosing
|
you do not specify it, it is inherited from the innermost enclosing
|
||||||
:meth:`within` block.
|
:meth:`within` block.
|
||||||
|
|
||||||
It should be noted that if the last message-receiving assertion of the block is
|
It should be noted that if the last message-receiving assertion of the block is
|
||||||
|
|
@ -461,8 +475,9 @@ B``, as long as a certain protocol is obeyed.
|
||||||
|
|
||||||
.. includecode:: ../../akka-testkit/src/test/scala/akka/testkit/TestProbeSpec.scala#autopilot
|
.. 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
|
The :meth:`run` method must return the auto-pilot for the next message, which
|
||||||
in an :class:`Option`; setting it to :obj:`None` terminates the auto-pilot.
|
may be :class:`KeepRunning` to retain the current one or :class:`NoAutoPilot`
|
||||||
|
to switch it off.
|
||||||
|
|
||||||
Caution about Timing Assertions
|
Caution about Timing Assertions
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
@ -471,23 +486,13 @@ The behavior of :meth:`within` blocks when using test probes might be perceived
|
||||||
as counter-intuitive: you need to remember that the nicely scoped deadline as
|
as counter-intuitive: you need to remember that the nicely scoped deadline as
|
||||||
described :ref:`above <TestKit.within>` is local to each probe. Hence, probes
|
described :ref:`above <TestKit.within>` is local to each probe. Hence, probes
|
||||||
do not react to each other's deadlines or to the deadline set in an enclosing
|
do not react to each other's deadlines or to the deadline set in an enclosing
|
||||||
:class:`TestKit` instance::
|
:class:`TestKit` instance:
|
||||||
|
|
||||||
class SomeTest extends TestKit(_system: ActorSystem) with ImplicitSender {
|
.. includecode:: code/docs/testkit/TestkitDocSpec.scala#test-within-probe
|
||||||
|
|
||||||
val probe = TestProbe()
|
Here, the ``expectMsg`` call will use the default timeout.
|
||||||
|
|
||||||
within(100 millis) {
|
.. _Scala-CallingThreadDispatcher:
|
||||||
probe.expectMsg("hallo") // Will hang forever!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
This test will hang indefinitely, because the :meth:`expectMsg` call does not
|
|
||||||
see any deadline. Currently, the only option is to use ``probe.within`` in the
|
|
||||||
above code to make it work; later versions may include lexically scoped
|
|
||||||
deadlines using implicit arguments.
|
|
||||||
|
|
||||||
.. _TestCallingThreadDispatcherRef:
|
|
||||||
|
|
||||||
CallingThreadDispatcher
|
CallingThreadDispatcher
|
||||||
=======================
|
=======================
|
||||||
|
|
@ -586,7 +591,7 @@ has to offer:
|
||||||
exception stack traces
|
exception stack traces
|
||||||
- Exclusion of certain classes of dead-lock scenarios
|
- Exclusion of certain classes of dead-lock scenarios
|
||||||
|
|
||||||
.. _actor.logging:
|
.. _actor.logging-scala:
|
||||||
|
|
||||||
Tracing Actor Invocations
|
Tracing Actor Invocations
|
||||||
=========================
|
=========================
|
||||||
|
|
|
||||||
329
akka-testkit/src/main/java/akka/testkit/JavaTestKit.java
Normal file
329
akka-testkit/src/main/java/akka/testkit/JavaTestKit.java
Normal file
|
|
@ -0,0 +1,329 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
package akka.testkit;
|
||||||
|
|
||||||
|
import scala.runtime.AbstractFunction0;
|
||||||
|
import akka.actor.ActorRef;
|
||||||
|
import akka.actor.ActorSystem;
|
||||||
|
import akka.event.Logging;
|
||||||
|
import akka.event.Logging.LogEvent;
|
||||||
|
import akka.japi.PurePartialFunction;
|
||||||
|
import akka.japi.CachingPartialFunction;
|
||||||
|
import akka.japi.Util;
|
||||||
|
import akka.util.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java API for the TestProbe. Proper JavaDocs to come once JavaDoccing is implemented.
|
||||||
|
*/
|
||||||
|
public class JavaTestKit {
|
||||||
|
private final TestProbe p;
|
||||||
|
|
||||||
|
public JavaTestKit(ActorSystem system) {
|
||||||
|
p = new TestProbe(system);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorRef getRef() {
|
||||||
|
return p.ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorSystem getSystem() {
|
||||||
|
return p.system();
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Duration duration(String s) {
|
||||||
|
return Duration.parse(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration dilated(Duration d) {
|
||||||
|
return d.mul(TestKitExtension.get(p.system()).TestTimeFactor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean msgAvailable() {
|
||||||
|
return p.msgAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorRef getLastSender() {
|
||||||
|
return p.lastMessage().sender();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(ActorRef actor, Object msg) {
|
||||||
|
actor.tell(msg, p.ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forward(ActorRef actor) {
|
||||||
|
actor.tell(p.lastMessage().msg(), p.lastMessage().sender());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reply(Object msg) {
|
||||||
|
p.lastMessage().sender().tell(msg, p.ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getRemainingTime() {
|
||||||
|
return p.remaining();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getRemainingTimeOr(Duration def) {
|
||||||
|
return p.remainingOr(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorRef watch(ActorRef ref) {
|
||||||
|
return p.watch(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorRef unwatch(ActorRef ref) {
|
||||||
|
return p.unwatch(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class IgnoreMsg {
|
||||||
|
abstract protected boolean ignore(Object msg);
|
||||||
|
|
||||||
|
public IgnoreMsg() {
|
||||||
|
p.ignoreMsg(new PurePartialFunction<Object, Object>() {
|
||||||
|
public Boolean apply(Object in, boolean isCheck) {
|
||||||
|
return ignore(in);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ignoreNoMsg() {
|
||||||
|
p.ignoreNoMsg();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoPilot(TestActor.AutoPilot pilot) {
|
||||||
|
p.setAutoPilot(pilot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class Within {
|
||||||
|
protected abstract void run();
|
||||||
|
|
||||||
|
public Within(Duration max) {
|
||||||
|
p.within(max, new AbstractFunction0<Object>() {
|
||||||
|
public Object apply() {
|
||||||
|
run();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Within(Duration min, Duration max) {
|
||||||
|
p.within(min, max, new AbstractFunction0<Object>() {
|
||||||
|
public Object apply() {
|
||||||
|
run();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class AwaitCond {
|
||||||
|
protected abstract boolean cond();
|
||||||
|
|
||||||
|
public AwaitCond() {
|
||||||
|
this(Duration.Undefined(), p.awaitCond$default$3());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AwaitCond(Duration max) {
|
||||||
|
this(max, p.awaitCond$default$3());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AwaitCond(Duration max, Duration interval) {
|
||||||
|
p.awaitCond(new AbstractFunction0<Object>() {
|
||||||
|
public Object apply() {
|
||||||
|
return cond();
|
||||||
|
}
|
||||||
|
}, max, interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ExpectMsg<T> {
|
||||||
|
private final T result;
|
||||||
|
|
||||||
|
public ExpectMsg(String hint) {
|
||||||
|
this(Duration.Undefined(), hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExpectMsg(Duration max, String hint) {
|
||||||
|
final Object received = p.receiveOne(max);
|
||||||
|
try {
|
||||||
|
result = match(received);
|
||||||
|
} catch (PurePartialFunction.NoMatchException ex) {
|
||||||
|
throw new AssertionError("while expecting '" + hint
|
||||||
|
+ "' received unexpected: " + received);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected T match(Object msg);
|
||||||
|
|
||||||
|
protected RuntimeException noMatch() {
|
||||||
|
throw PurePartialFunction.noMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T get() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T expectMsgEquals(T msg) {
|
||||||
|
return p.expectMsg(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T expectMsgEquals(Duration max, T msg) {
|
||||||
|
return p.expectMsg(max, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T expectMsgClass(Class<T> clazz) {
|
||||||
|
return p.expectMsgClass(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T expectMsgClass(Duration max, Class<T> clazz) {
|
||||||
|
return p.expectMsgClass(max, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object expectMsgAnyOf(Object... msgs) {
|
||||||
|
return p.expectMsgAnyOf(Util.arrayToSeq(msgs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object expectMsgAnyOf(Duration max, Object... msgs) {
|
||||||
|
return p.expectMsgAnyOf(max, Util.arrayToSeq(msgs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] expectMsgAllOf(Object... msgs) {
|
||||||
|
return (Object[]) p.expectMsgAllOf(Util.arrayToSeq(msgs)).toArray(
|
||||||
|
Util.manifest(Object.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] expectMsgAllOf(Duration max, Object... msgs) {
|
||||||
|
return (Object[]) p.expectMsgAllOf(max, Util.arrayToSeq(msgs)).toArray(
|
||||||
|
Util.manifest(Object.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T expectMsgAnyClassOf(Class<? extends T>... classes) {
|
||||||
|
final Object result = p.expectMsgAnyClassOf(Util.arrayToSeq(classes));
|
||||||
|
return (T) result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object expectMsgAnyClassOf(Duration max, Class<?>... classes) {
|
||||||
|
return p.expectMsgAnyClassOf(max, Util.arrayToSeq(classes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expectNoMsg() {
|
||||||
|
p.expectNoMsg();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expectNoMsg(Duration max) {
|
||||||
|
p.expectNoMsg(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ReceiveWhile<T> {
|
||||||
|
abstract protected T match(Object msg);
|
||||||
|
|
||||||
|
private Object results;
|
||||||
|
|
||||||
|
public ReceiveWhile(Class<T> clazz) {
|
||||||
|
this(clazz, Duration.Undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReceiveWhile(Class<T> clazz, Duration max) {
|
||||||
|
this(clazz, max, Duration.Inf(), Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReceiveWhile(Class<T> clazz, Duration max, int messages) {
|
||||||
|
this(clazz, max, Duration.Inf(), messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public ReceiveWhile(Class<T> clazz, Duration max, Duration idle, int messages) {
|
||||||
|
results = p.receiveWhile(max, idle, messages,
|
||||||
|
new CachingPartialFunction<Object, T>() {
|
||||||
|
public T match(Object msg) {
|
||||||
|
return ReceiveWhile.this.match(msg);
|
||||||
|
}
|
||||||
|
}).toArray(Util.manifest(clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RuntimeException noMatch() {
|
||||||
|
throw PurePartialFunction.noMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T[] get() {
|
||||||
|
return (T[]) results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class EventFilter<T> {
|
||||||
|
abstract protected T run();
|
||||||
|
|
||||||
|
private final Class<? extends Logging.LogEvent> clazz;
|
||||||
|
|
||||||
|
private String source = null;
|
||||||
|
private String message = null;
|
||||||
|
private boolean pattern = false;
|
||||||
|
private boolean complete = false;
|
||||||
|
private int occurrences = Integer.MAX_VALUE;
|
||||||
|
private Class<? extends Throwable> exceptionType = null;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public EventFilter(Class<?> clazz) {
|
||||||
|
if (Throwable.class.isAssignableFrom(clazz)) {
|
||||||
|
this.clazz = Logging.Error.class;
|
||||||
|
exceptionType = (Class<? extends Throwable>) clazz;
|
||||||
|
} else if (Logging.LogEvent.class.isAssignableFrom(clazz)) {
|
||||||
|
this.clazz = (Class<? extends LogEvent>) clazz;
|
||||||
|
} else throw new IllegalArgumentException("supplied class must either be LogEvent or Throwable");
|
||||||
|
}
|
||||||
|
|
||||||
|
public T exec() {
|
||||||
|
akka.testkit.EventFilter filter;
|
||||||
|
if (clazz == Logging.Error.class) {
|
||||||
|
if (exceptionType == null) exceptionType = Logging.noCause().getClass();
|
||||||
|
filter = new ErrorFilter(exceptionType, source, message, pattern, complete, occurrences);
|
||||||
|
} else if (clazz == Logging.Warning.class) {
|
||||||
|
filter = new WarningFilter(source, message, pattern, complete, occurrences);
|
||||||
|
} else if (clazz == Logging.Info.class) {
|
||||||
|
filter = new InfoFilter(source, message, pattern, complete, occurrences);
|
||||||
|
} else if (clazz == Logging.Debug.class) {
|
||||||
|
filter = new DebugFilter(source, message, pattern, complete, occurrences);
|
||||||
|
} else throw new IllegalArgumentException("unknown LogLevel " + clazz);
|
||||||
|
return filter.intercept(new AbstractFunction0<T>() {
|
||||||
|
public T apply() {
|
||||||
|
return run();
|
||||||
|
}
|
||||||
|
}, p.system());
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventFilter<T> message(String msg) {
|
||||||
|
message = msg;
|
||||||
|
pattern = false;
|
||||||
|
complete = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventFilter<T> startsWith(String msg) {
|
||||||
|
message = msg;
|
||||||
|
pattern = false;
|
||||||
|
complete = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventFilter<T> matches(String regex) {
|
||||||
|
message = regex;
|
||||||
|
pattern = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventFilter<T> from(String source) {
|
||||||
|
this.source = source;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventFilter<T> occurrences(int number) {
|
||||||
|
occurrences = number;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -136,5 +136,5 @@ object TestActorRef {
|
||||||
/**
|
/**
|
||||||
* Java API
|
* Java API
|
||||||
*/
|
*/
|
||||||
def create(props: Props, name: String, system: ActorSystem) = apply(props, name)(system)
|
def create[T <: Actor](system: ActorSystem, props: Props, name: String): TestActorRef[T] = apply(props, name)(system)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,23 @@ import akka.actor.ActorSystem
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import akka.util.BoxedType
|
import akka.util.BoxedType
|
||||||
import scala.annotation.varargs
|
import scala.annotation.varargs
|
||||||
|
import akka.japi.PurePartialFunction
|
||||||
|
|
||||||
object TestActor {
|
object TestActor {
|
||||||
type Ignore = Option[PartialFunction[AnyRef, Boolean]]
|
type Ignore = Option[PartialFunction[AnyRef, Boolean]]
|
||||||
|
|
||||||
trait AutoPilot {
|
abstract class AutoPilot {
|
||||||
def run(sender: ActorRef, msg: Any): Option[AutoPilot]
|
def run(sender: ActorRef, msg: Any): AutoPilot
|
||||||
|
def noAutoPilot: AutoPilot = NoAutoPilot
|
||||||
|
def keepRunning: AutoPilot = KeepRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
case object NoAutoPilot extends AutoPilot {
|
||||||
|
def run(sender: ActorRef, msg: Any): AutoPilot = this
|
||||||
|
}
|
||||||
|
|
||||||
|
case object KeepRunning extends AutoPilot {
|
||||||
|
def run(sender: ActorRef, msg: Any): AutoPilot = sys.error("must not call")
|
||||||
}
|
}
|
||||||
|
|
||||||
case class SetIgnore(i: Ignore)
|
case class SetIgnore(i: Ignore)
|
||||||
|
|
@ -43,15 +54,18 @@ class TestActor(queue: BlockingDeque[TestActor.Message]) extends Actor {
|
||||||
|
|
||||||
var ignore: Ignore = None
|
var ignore: Ignore = None
|
||||||
|
|
||||||
var autopilot: Option[AutoPilot] = None
|
var autopilot: AutoPilot = NoAutoPilot
|
||||||
|
|
||||||
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 SetAutoPilot(pilot) ⇒ autopilot = pilot
|
||||||
case x: AnyRef ⇒
|
case x: AnyRef ⇒
|
||||||
autopilot = autopilot.flatMap(_.run(sender, x))
|
autopilot = autopilot.run(sender, x) match {
|
||||||
|
case KeepRunning ⇒ autopilot
|
||||||
|
case other ⇒ other
|
||||||
|
}
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
@ -126,20 +140,20 @@ trait TestKitBase {
|
||||||
* Have the testActor watch someone (i.e. `context.watch(...)`). Waits until
|
* Have the testActor watch someone (i.e. `context.watch(...)`). Waits until
|
||||||
* the Watch message is received back using expectMsg.
|
* the Watch message is received back using expectMsg.
|
||||||
*/
|
*/
|
||||||
def watch(ref: ActorRef) {
|
def watch(ref: ActorRef): ActorRef = {
|
||||||
val msg = TestActor.Watch(ref)
|
val msg = TestActor.Watch(ref)
|
||||||
testActor ! msg
|
testActor ! msg
|
||||||
expectMsg(msg)
|
expectMsg(msg).ref
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Have the testActor stop watching someone (i.e. `context.unwatch(...)`). Waits until
|
* Have the testActor stop watching someone (i.e. `context.unwatch(...)`). Waits until
|
||||||
* the Watch message is received back using expectMsg.
|
* the Watch message is received back using expectMsg.
|
||||||
*/
|
*/
|
||||||
def unwatch(ref: ActorRef) {
|
def unwatch(ref: ActorRef): ActorRef = {
|
||||||
val msg = TestActor.UnWatch(ref)
|
val msg = TestActor.UnWatch(ref)
|
||||||
testActor ! msg
|
testActor ! msg
|
||||||
expectMsg(msg)
|
expectMsg(msg).ref
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -242,22 +256,6 @@ trait TestKitBase {
|
||||||
*/
|
*/
|
||||||
def within[T](max: Duration)(f: ⇒ T): T = within(0 seconds, max)(f)
|
def within[T](max: Duration)(f: ⇒ T): T = within(0 seconds, max)(f)
|
||||||
|
|
||||||
/**
|
|
||||||
* Java API for within():
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* new Within(Duration.parse("3 seconds")) {
|
|
||||||
* public void run() {
|
|
||||||
* // your test code here
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }}}
|
|
||||||
*/
|
|
||||||
abstract class Within(max: Duration) {
|
|
||||||
def run(): Unit
|
|
||||||
within(max)(run())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as `expectMsg(remaining, obj)`, but correctly treating the timeFactor.
|
* Same as `expectMsg(remaining, obj)`, but correctly treating the timeFactor.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@
|
||||||
|
|
||||||
package akka.testkit;
|
package akka.testkit;
|
||||||
|
|
||||||
import org.junit.Test;
|
import akka.actor.Actor;
|
||||||
import akka.actor.Props;
|
import akka.actor.Props;
|
||||||
|
|
||||||
public class TestActorRefJavaCompile {
|
public class TestActorRefJavaCompile {
|
||||||
|
|
||||||
public void shouldBeAbleToCompileWhenUsingApply() {
|
public void shouldBeAbleToCompileWhenUsingApply() {
|
||||||
//Just a dummy call to make sure it compiles
|
//Just a dummy call to make sure it compiles
|
||||||
TestActorRef ref = TestActorRef.apply(new Props(), null);
|
TestActorRef<Actor> ref = TestActorRef.apply(new Props(), null);
|
||||||
|
ref.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,10 +44,10 @@ class TestProbeSpec extends AkkaSpec with DefaultTimeout {
|
||||||
//#autopilot
|
//#autopilot
|
||||||
val probe = TestProbe()
|
val probe = TestProbe()
|
||||||
probe.setAutoPilot(new TestActor.AutoPilot {
|
probe.setAutoPilot(new TestActor.AutoPilot {
|
||||||
def run(sender: ActorRef, msg: Any): Option[TestActor.AutoPilot] =
|
def run(sender: ActorRef, msg: Any): TestActor.AutoPilot =
|
||||||
msg match {
|
msg match {
|
||||||
case "stop" ⇒ None
|
case "stop" ⇒ TestActor.NoAutoPilot
|
||||||
case x ⇒ testActor.tell(x, sender); Some(this)
|
case x ⇒ testActor.tell(x, sender); TestActor.KeepRunning
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
//#autopilot
|
//#autopilot
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue