Reputation: 10325
Lately I tried to write some unit tests for akka actors to test actors messages flow. I observed some strange behaviour in my tests:
Fields:
private TestActorRef<Actor> sut;
private ActorSystem system;
JavaTestKit AnotherActor;
JavaTestKit YetAnotherActor;
System and actors are created in @Before annotated method:
@Before
public void setup() throws ClassNotFoundException {
system = ActorSystem.apply();
AnotherActor = new JavaTestKit(system);
YetAnotherActor = new JavaTestKit(system);
Props props = MyActor.props(someReference);
this.sut = system.of(props, "MyActor"); }
Next
@Test
public void shouldDoSth() throws Exception {
// given actor
MyActor actor = (MyActor) sut.underlyingActor();
// when
SomeMessage message = new SomeMessage(Collections.emptyList());
sut.tell(message, AnotherActor.getRef());
// then
YetAnotherActor.expectMsgClass(
FiniteDuration.apply(1, TimeUnit.SECONDS),
YetSomeMessage.class);
}
In my code I have:
private void processMessage(SomeMessage message) {
final List<Entity> entities = message.getEntities();
if(entities.isEmpty()) {
YetAnotherActor.tell(new YetSomeMessage(), getSelf());
// return;
}
if (entities > workers.size()) {
throw new IllegalStateException("too many tasks to be started !");
}
}
Basically, sometimes (very rarely) such test fails (on another OS), and the exception from processMessage method is thrown (IllegalStateException due to business logic).
Mostly test pass as YetSomeMessage message is received by YetAnotherActor despite the fact that the IllegateStateException error is thrown as well and logged in stack trace.
As I assume from akka TestActorRef documentation:
This special ActorRef is exclusively for use during unit testing in a single-threaded environment. Therefore, it overrides the dispatcher to CallingThreadDispatcher and sets the receiveTimeout to None. Otherwise, it acts just like a normal ActorRef. You may retrieve a reference to the underlying actor to test internal logic.
my system is using only single thread to process messages received by actor. Could someone explain my why despite proper assertion, the test fails ?
Of course returning after sending YetSomeMessage in proper code would be done but I do not understand how another thread processing can lead to test faiulre.
Upvotes: 1
Views: 3044
Reputation: 1863
Since you are using TestActorRef, you are basically doing synchronous testing. As a general rule of thumb, don't use TestActorRef unless you really need to. That thing uses the CallingThreadDispatcher, i.e. it will steal the callers thread to execute the actor. So the solution to your mystery is that the actor runs on the same thread as your test and therefore the exception ends up on the test thread.
Fortunately, this test-case of yours do not need the TestActorRef at all. You can just create the actor as an ordinary one, and everything should work (i.e. the actor will be on a proper separate thread). Please try to do everything with the asynchronous test support http://doc.akka.io/docs/akka/2.4.0/scala/testing.html#Asynchronous_Integration_Testing_with_TestKit
Upvotes: 2