If you're looking for a general introduction into using Akka from Java have a look at this post
In a recent project I've been using Akka for a concurrent producer-consumer setup. It is an actor framework for the JVM that is implemented in Scala but provides a Java API so normally you don't notice that your dealing with a Scala library.
Most of my business code is encapsulated in services that don't depend on Akka and can therefore be tested in isolation. But for some cases I've been looking for a way to test the behaviour of the actors. As I struggled with this for a while and didn't find a real howto on testing Akka actors from Java I hope my notes might be useful for other people as well.
The main problem when testing actors is that they are managed objects and you can't just instanciate them. Akka comes with a module for tests that is documented well for using it from Scala. But besides the note that it's possible you don't find a lot of information on using it from Java.
When using Maven you need to make sure that you have the akka-testkit dependency in place:
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-testkit</artifactId>
<version>2.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
I will show you how to implement a test for the actors that are introduced in the Akka java tutorial. It involves one actor that does a substep of calculating Pi for a certain start number and a given number of elements.
To test this actor we need a way to set it up. Akka-testkit provides a helper TestActorRef that can be used to set it up. Using scala this seems to be rather simple:
val testActor = TestActorRef[Worker]
If you try to do this from Java you will notice that you can't use a similar call. I have to admit that I am not quite sure yet what is going on. I would have expected that there is an apply() method on the TestActorRef companion object that uses some kind of implicits to instanciate the Worker object. But when inspecting the sources the thing that comes closest to it is this definition:
def apply[T <: Actor](factory: ⇒ T)(implicit system: ActorSystem)
No sign of implicit for the factory. Something I still have to investigate further.
To use it from Java you can use the method apply that takes a reference to a Function0 and an actor system. The actor system can be setup easily using
actorSystem = ActorSystem.apply();
The apply() method is very important in scala as it's kind of the default method for objects. For example myList(1) is internally using myList.apply(1).
TestActorRef workerRef = TestActorRef.apply(new Function0() {
@Override
public Worker apply() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void apply$mcV$sp() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean apply$mcZ$sp() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public byte apply$mcB$sp() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public short apply$mcS$sp() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public char apply$mcC$sp() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public int apply$mcI$sp() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public long apply$mcJ$sp() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public float apply$mcF$sp() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public double apply$mcD$sp() {
throw new UnsupportedOperationException("Not supported yet.");
}
}, actorSystem);
The only method we really are interested in is the normal apply method. Where do those other methods come from? There is no obvious hint in the scaladocs.
During searching for the solution I found a mailing list thread that explains some of the magic. The methods are performance optimizations for boxing and unboxing that are automatically generated by the scala compiler for the @specialized annotation. Still, I am unsure about why this is happening exactly. According to this presentation I would have expected that I am using the specialized instance for Object, maybe that is something special regarding traits?
Fortunately we don't really need to implement the interface ourself: There's an adapter class, AbstractFunction0, that makes your code look much nicer:
@Before
public void initActor() {
actorSystem = ActorSystem.apply();
actorRef = TestActorRef.apply(new AbstractFunction0() {
@Override
public Pi.Worker apply() {
return new Pi.Worker();
}
}, actorSystem);
}
This is like I would have expected it to behave in the first place.
Now, as we have setup our test we can use the TestActorRef to really test the actor. For example we can test that the actor doesn't do anything for a String message:
@Test
public void doNothingForString() {
TestProbe testProbe = TestProbe.apply(actorSystem);
actorRef.tell("Hello", testProbe.ref());
testProbe.expectNoMsg(Duration.apply(100, TimeUnit.MILLISECONDS));
}
TestProbe is another helper that can be used to check the messages that are sent between cooperating actors. In this example we are checking that no message is passed to the sender for 100 miliseconds, which should be enough for execution.
Let's test some real functionality. Send a message to the actor and check that the result message is send:
@Test
public void calculatePiFor0() {
TestProbe testProbe = TestProbe.apply(actorSystem);
Pi.Work work = new Pi.Work(0, 0);
actorRef.tell(work, testProbe.ref());
testProbe.expectMsgClass(Pi.Result.class);
TestActor.Message message = testProbe.lastMessage();
Pi.Result resultMsg = (Pi.Result) message.msg();
assertEquals(0.0, resultMsg.getValue(), 0.0000000001);
}
Now we use the TestProbe to block until a message arrives. When it's there we can have a look at using the lastMessage().
You can look at the rest of the test on Github. Comments are more than welcome as I am pretty new to Scala as well as Akka.
Update
As Jonas Bonér points out I've been using the Scala API. Using the Props class the setup is easier:
@Before
public void initActor() {
actorSystem = ActorSystem.apply();
actorRef = TestActorRef.apply(new Props(Pi.Worker.class), actorSystem);
}
About Florian Hopf
I am working as a freelance software developer and consultant in Karlsruhe, Germany and have written a German book about Elasticsearch. If you liked this post you can follow me on Twitter or subscribe to my feed to get notified of new posts. If you think I could help you and your company and you'd like to work with me please contact me directly.
Very well written. There is very little documentation for using Akka for Java. This was a big help. Thank you
AntwortenLöschenThis was really helpful. Thanks
AntwortenLöschenCONTRIB: You can create the ActorSystem by ActorSystem.create("")