2

I am trying to write unit test for one of my actors which is derived from AbstractPersistentActorWithAtLeastOnceDelivery using TestKit. I need to create an actor with TestActorRef.create(...) since I need to get an underlyingActor in order to inject the mocks into actor's implementation.

My (simplified) actor

public class MyActor extends AbstractPersistentActorWithAtLeastOnceDelivery {

@Override
public Receive createReceive() {
    return receiveBuilder().match(String.class, message -> {
        persist(new MessageSent(message), event -> updateState(event));
    }).match(ConfirmMessage.class, confirm -> {
        persist(new MessageConfirmed(confirm.deliveryId), event -> 
         updateState(event));
    }).matchAny(message -> log.info("Received unexpected message of class {}. 
     Content: {}", message.getClass().getName(), message.toString())).build();

}

 void updateState(Object received) {
    if (received instanceof MessageSent) {
        final MessageSent messageSent = (MessageSent) received;
        ActorRef destinationActor = 
        findDestinationActor(messageSent.messageData);               
        deliver(actorSystem.actorSelection(destinationActor.path()), 
    deliveryId -> new Message(deliveryId, messageSent.messageData));
    } else if (received instanceof MessageConfirmed) {
        final MessageConfirmed messageConfirmed = (MessageConfirmed) received;
        confirmDelivery(messageConfirmed.deliveryId);
    }
}

Unit test:

@Test
public void actorTest() {
  ActorSystem system = ActorSystem.create();
  TestKit probe = new TestKit(system);
  TestActorRef<myActor> testActor = TestActorRef.create(system, props, 
     probe.getRef());
  MyActor myActor = testActor.underlyingActor();
  injectMocks(myActor); // my method
  testActor.tell("testMessage", probe.getRef());
  List<Object> receivedMessages = probe.receiveN(1, FiniteDuration.create(3, 
    TimeUnit.SECONDS));

}

In debugger I see that deliver() method inside updateState() is called, but the unit test fails with error:

assertion failed: timeout (3 seconds) while expecting 1 messages (got 0)

I am wondering if it is possible at to use the TestKit to test an actor created via TestActorRef and if the fact that my actor extends AbstractPersistentActorWithAtLeastOnceDelivery has something to do with tests failure

MrkK
  • 873
  • 3
  • 12
  • 22

1 Answers1

0

It is not possible to use TestActorRef with Akka persistence. It sometimes works but often fails in ways you described in your question. Other features of TestKit work fine with Akka persistence.

See the warning under https://doc.akka.io/docs/akka/current/testing.html#synchronous-testing-testactorref:

Warning

Due to the synchronous nature of TestActorRef it will not work with some support traits that Akka provides as they require asynchronous behaviors to function properly. Examples of traits that do not mix well with test actor refs are PersistentActor and AtLeastOnceDelivery provided by Akka Persistence.

Frederic A.
  • 3,504
  • 10
  • 17
  • Does it mean that a persistent actor which uses dependency injection cannot be unit tested? One would need access to actors class instance to inject mocks or real dependencies and it can only be done via TestActorRef – MrkK Mar 22 '18 at 00:25
  • You could maybe work around it by testing using something like `class MyActorUnderTest extends MyActor` in which you set your mocks before actor creation – Frederic A. Mar 22 '18 at 08:48