Tuesday, May 8, 2012

Using TestKit with Java

Unit testing toolkit is provided via TestKit in Akka. The scala side of unit testing is well covered. For java, TestKit provides limited constructs. The various examples implemented by Ray Roestenburg have been ported to Java world, with couple of more scenario's added. This can be good starting point for Java programmers to start unit testing their actors.

Let’s check out some testing code which tests out the following different set of actors
  • Echo Actor – responds back with whatever is passed to the actor
  • Forwarding Actor – forwards the message to another actor
  • Sequencing Actor – replies back in a series of messages but assuming we are interested in one
  • Filtering Actor – replies back for certain messages and ignores the others
  • Boom Actor – throws an exception when a message is send
  • Supervisor Actor – manages an another worker actor, and based on the exception thrown by the worker actor, applies the appropriate supervisor strategy


@Test
public void testEchoActor() {
ActorRef echoActorRef = _system.actorOf(new Props(EchoActor.class));
// pass the reference to implicit sender testActor() otherwise
// message end up in dead mailbox
echoActorRef.tell("Hi there", super.testActor());
expectMsg("Hi there");
}
@Test
public void testForwardingActor() {
ActorRef forwardingActorRef = _system.actorOf(new Props(
new UntypedActorFactory() {
public UntypedActor create() {
return new ForwardingActor(testActor());
}
}));
// pass the reference to implicit sender testActor() otherwise
// message end up in dead mailbox
forwardingActorRef.tell("test message", super.testActor());
expectMsg("test message");
}
@Test
public void testSequencingActor() {
final List<Integer> headList = new ArrayList<Integer>();
final List<Integer> tailList = new ArrayList<Integer>();
int randomHead = new Random().nextInt(6);
int randomTail = new Random().nextInt(10);
for (int i = 0; i < randomHead; i++)
headList.add(i);
for (int i = 1; i < randomTail; i++)
tailList.add(i);
TestActorRef<SequencingActor> sequencingActorRef = TestActorRef.apply(
new Props(new UntypedActorFactory() {
public UntypedActor create() {
return new SequencingActor(testActor(), headList,
tailList);
}
}), _system);
// pass the reference to implicit sender testActor() otherwise
// message end up in dead mailbox
sequencingActorRef.tell("do something", super.testActor());
for (Integer value : headList) {
expectMsgClass(Integer.class);
}
expectMsg("do something");
for (Integer value : tailList) {
expectMsgClass(Integer.class);
}
}
@Test
public void testFilteringActor() {
TestActorRef<FilteringActor> filteringActorRef = TestActorRef.apply(
new Props(new UntypedActorFactory() {
public UntypedActor create() {
return new FilteringActor(testActor());
}
}), _system);
// pass the reference to implicit sender testActor() otherwise
// message end up in dead mailbox
// first test
filteringActorRef.tell("test message", super.testActor());
expectMsg("test message");
// second test
filteringActorRef.tell(1, super.testActor());
expectNoMsg();
}
/**
* if you want to test whether the Supervisor strategy is working fine
*/
@Test
public void testSupervisorStrategy1() throws Exception {
final TestActorRef<SupervisorActor> supervisorActorRef1 = TestActorRef
.apply(new Props(new UntypedActorFactory() {
public UntypedActor create() {
return new SupervisorActor();
}
}), "supervisor1", _system);
Duration timeout = Duration.parse("5 second");
// register the BoomActor with the Supervisor
final ActorRef child = (ActorRef) Await.result(
ask(supervisorActorRef1, new Props(BoomActor.class), 5000),
timeout);
child.tell(123);
Assert.assertFalse(child.isTerminated());
}
@Test
public void testSupervisorStrategy2() throws Exception {
TestActorRef<SupervisorActor> supervisorActorRef2 = TestActorRef.apply(
new Props(new UntypedActorFactory() {
public UntypedActor create() {
return new SupervisorActor();
}
}), "supervisor2", _system);
final TestProbe probe = new TestProbe(_system);
// register the BoomActor with the Supervisor
final ActorRef child = (ActorRef) Await.result(
ask(supervisorActorRef2, new Props(BoomActor.class), 5000),
Duration.parse("5 second"));
probe.watch(child);
// second check
child.tell("do something");
probe.expectMsg(new Terminated(child));
}
@Test
public void testBoomActor() {
final TestActorRef child = TestActorRef.apply(
new Props(BoomActor.class), _system);
try {
child.receive("do something");
//should not reach here
Assert.assertTrue(false);
} catch (IllegalArgumentException e) {
Assert.assertEquals(e.getMessage(), "boom!");
}
}
The code base for the program is available at the following location - https://github.com/write2munish/Akka-Essentials under the AkkaUnitTest

7 comments:

  1. I moved your two tests from src/main/java to src/test/java and get the following error:

    com.typesafe.config.ConfigException$Missing: No configuration setting found for key 'TestSys'

    Is there supposed to be an application.conf in the classpath?

    ReplyDelete
  2. Yes, there is one and you will find the same under src/main/resource.

    ReplyDelete
  3. I forgot to say a big thank you for publishing these examples.

    ReplyDelete
  4. My attempted at doing a git push failed, so here are the changes I made:
    - Moved all of your test classes to src/test/java/org.../actors
    - Renamed UnitTestExample.java to ExampleUnitTest.java
    - Renamed resource/ to resources/

    Now I can run mvn clean install and all tests are executed.

    Robin

    ReplyDelete
  5. Thanks Robin. I will make the changes and push them out.

    ReplyDelete
  6. I think there's are an issue preventing testSupervisorStrategy1() from actually testing what it wants to test: While child.tell(123) will cause the "child" actor to eventually blow up and be restarted by its supervisor, Akka gives no guarantee as to when this happens.

    Thus the final assertFalse(child.isTerminated()) can potentially be executed before the child has reacted to the 123 message, causing isTerminated() to yield false regardless of whether the supervision strategy is working properly or not.

    ReplyDelete
  7. When you make use of TestActorRef, it sets the dispatcher to CallingThreadDispatcher and sets the receiveTimeout to None

    ReplyDelete

Trackback

loading..