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.