Search code examples
javaunit-testingakkaakka-testkit

Unit testing private methods in Akka


I'm new to akka and I'm trying akka on java. I'd like to understand unit testing of business logic within actors. I read documentation and the only example of isolated business logic within actor is:

static class MyActor extends UntypedActor {
  public void onReceive(Object o) throws Exception {
    if (o.equals("say42")) {
      getSender().tell(42, getSelf());
    } else if (o instanceof Exception) {
      throw (Exception) o;
    }
  }
  public boolean testMe() { return true; }
}

@Test
public void demonstrateTestActorRef() {
  final Props props = Props.create(MyActor.class);
  final TestActorRef<MyActor> ref = TestActorRef.create(system, props, "testA");
  final MyActor actor = ref.underlyingActor();
  assertTrue(actor.testMe());
}

While this is simple, it implies that the method I want to test is public. However, considering actors should communicate only via messages, my understanding that there is no reason to have public methods, so I'd made my method private. Like in example below:

public class LogRowParser extends AbstractActor {
    private final Logger logger = LoggerFactory.getLogger(LogRowParser.class);

    public LogRowParser() {
        receive(ReceiveBuilder.
                        match(LogRow.class, lr -> {                                
                            ParsedLog log = parse(lr.rowText);
                            final ActorRef logWriter = getContext().actorOf(Props.create(LogWriter.class));
                            logWriter.tell(log, self());
                        }).
                        matchAny(o -> logger.info("Unknown message")).build()
        );
    }

    private ParsedLog parse(String rowText) {
        // Log parsing logic
    }
}

So to test method parse I either:

  1. need it to make package-private
  2. Or test actor's public interface, i.e. that next actor LogWriter received correct parsed message from my actor LogRowParser

My questions:

  1. Are there any downsides on option #1? Assuming that actors communicating only via messages, encapsulation and clean open interfaces are less important?
  2. In case if I try to use option #2, is there a way to catch messages sent from actor in test downstream (testing LogRowParser and catching in LogWriter)? I reviewed various examples on JavaTestKit but all of them are catching messages that are responses back to sender and none that would show how to intercept the message send to new actor.
  3. Is there another option that I'm missing?

Thanks!

UPD: Forgot to mention that I also considered options like:

  • Moving logic out of actors completely into helper classes. Is it common practice with akka?
  • Powermock... but i'm trying to avoid it if redesign is possible

Solution

  • There's really no good reason to make that method private. One generally makes a method on a class private to prevent someone who has a direct reference to an instance of that class from calling that method. With an actor instance, no one will have a direct reference to an instance of that actor class. All you can get to communicate with an instance of that actor class is an ActorRef which is a light weight proxy that only allows you to communicate by sending messages to be handled by onReceive via the mailbox. An ActorRef does not expose any internal state or methods of that actor class. That's sort of one of the big selling points of an actor system. An actor instance completely encapsulates its internal state and methods, protecting them from the outside world and only allows those internal things to change in response to receiving messages. That's why it does not seem necessary to mark that method as private.

    Edit

    Unit testing of an actor, IMO, should always go through the receive functionality. If you have some internal methods that are then called by the handling in receive, you should not focus on testing these methods in isolation but instead make sure that the paths that lead to their invocation are properly exercised via the messages that you pass during test scenarios.

    In your particular example, parse is producing a ParsedLog message that is then sent on to a logWriter child actor. For me, knowing that parse works as expected means asserting that the logWriter received the correct message. In order to do this, I would allow the creation of the child logWriter to be overridden and then do just that in the test code and replace the actor creation with a TestProbe. Then, you can use expectMsg on that probe to make sure that it received the expected ParsedLog message thus also testing the functionality in parse.

    As far as your other comment around moving the real business for the actor out into a separate and more testable class and then calling that from in the actor, some people do this, so it's not unheard of. I personally don't, but that's just me. If that approach works for you, I don't see any major issues with it.