Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test Akka Actor functionality by mocking one or more methods in it

I'm interested to know about how to test Akka Actor functionality, by mocking some methods (substitute real object's/actor's method implementation by mocked one) in Actor.

I use akka.testkit.TestActorRef;

Also: I tried to use SpyingProducer but it is not clear how to use it. (like I if I created actor inside its implementation it would be the same I have now). The google search result about that is not very verbose.

I use powemockito and java. But It does not matter. I would be interested to know how to do it in principle with any language with any framework

(so if you do not know how power/mockito works just provide your code.. (please) or complete idea about how you would do it with your tools you know.)

So, let's say we have an Actor to test:

package example.formock;

import akka.actor.UntypedActor;

public class ToBeTestedActor extends UntypedActor {

    @Override
    public void onReceive(Object message) throws Exception {

        if (message instanceof String) {
            getSender().tell( getHelloMessage((String) message), getSelf());
        }

    }

    String getHelloMessage(String initMessage) { // this was created for test purposes (for testing mocking/spy capabilities). Look at the test
        return "Hello, " + initMessage;
    }

}

And in our test we want to substitute getHelloMessage() returning something else.

This is my try:

package example.formock;

import akka.testkit.TestActorRef;
...

@RunWith(PowerMockRunner.class)
@PrepareForTest(ToBeTestedActor.class)
public class ToBeTestedActorTest {

    static final Timeout timeout = new Timeout(Duration.create(5, "seconds"));

    @Test
    public void getHelloMessage() {

        final ActorSystem system = ActorSystem.create("system");

        // given
        final TestActorRef<ToBeTestedActor> actorRef = TestActorRef.create(
                system,
                Props.create(ToBeTestedActor.class),
                "toBeTestedActor");

        // First try:
        ToBeTestedActor actorSpy = PowerMockito.spy(actorRef.underlyingActor());
        // change functionality
        PowerMockito.when(actorSpy.getHelloMessage (anyString())).thenReturn("nothing"); // <- expecting result   


        try {

           // when
           Future<Object> future = Patterns.ask(actorRef, "Bob", timeout);
           // then
           assertTrue(future.isCompleted());

            // when
           String resultMessage = (String) Await.result(future, Duration.Zero());
            // then
           assertEquals("nothing", resultMessage);  // FAIL HERE

        } catch (Exception e) {
           fail("ops");
        }
    }
}

Result:

org.junit.ComparisonFailure: 
Expected :nothing
Actual   :Hello, Bob
like image 822
ses Avatar asked May 23 '13 14:05

ses


People also ask

What is the difference between Akka testkit and actor model?

The actor model presents a different view on how units of code are delimited and how they interact, which influences how to perform tests. Akka comes with a dedicated module akka-testkit for supporting tests. Testkit allows you to test your actors in a controlled but realistic environment.

Why is testing in Akka so hard?

This is one case where Akka.NET’s commitment to providing simple abstractions makes testing harder. The simplest way to test this relationship is with messaging. For example, you can create a parent actor whose child messages another actor once it starts.

How do I use Akka testkit?

To use Akka Testkit, you must add the following dependency in your project: As with any piece of software, automated tests are a very important part of the development cycle. The actor model presents a different view on how units of code are delimited and how they interact, which influences how to perform tests.

How can I enable full logging of actor activities in Akka?

To summarize, you can enable full logging of actor activities using this configuration fragment: Akka’s test suite is written using ScalaTest, which also shines through in documentation examples. However, the TestKit and its facilities do not depend on that framework, so you can essentially use whichever suits your development style best.


2 Answers

Akka has a class AutoPilot that is basically a general mock for actors, with the ability to respond to messages and assert that messages were sent. http://doc.akka.io/docs/akka/snapshot/java/testing.html

Here's the java example for that page. You create a probe, set an auto-pilot that can respond to messages, and get an ActorRef from it that you can substitute in for your real actor.

new JavaTestKit(system) {{
  final JavaTestKit probe = new JavaTestKit(system);
  // install auto-pilot
  probe.setAutoPilot(new TestActor.AutoPilot() {
    public AutoPilot run(ActorRef sender, Object msg) {
      sender.tell(msg, ActorRef.noSender());
      return noAutoPilot();
    }
  });
  // first one is replied to directly ...
  probe.getRef().tell("hello", getRef());
  expectMsgEquals("hello");
  // ... but then the auto-pilot switched itself off
  probe.getRef().tell("world", getRef());
  expectNoMsg();
}};
like image 137
Douglas Rapp Avatar answered Sep 28 '22 00:09

Douglas Rapp


I have no experience in using Akka with Java, but I guess the solution for this I use in Scala can also apply to Java. There is no need at all to mock anything. In Java mocking is sometimes useful for testing, but my personal experience/opinion is that whenever you need PowerMock you're doing something wrong.

Here's how I try to test using Akka:

In Scala I use a trait (aka interface) in which the actor methods are defined.

trait ToBeTested {
  def getHelloMessage(msg: String, replyTarget: ActorRef): String = 
      replyTarget ! s"Hello $msg"
}

This way, this functionality can be unit tested very easy. For the real actor I try to stick to implement the receive method only.

class ToBeTestedActor extends Actor with ToBeTested {
  def receive: Receive = {
    case msg: String => getHelloMessage(msg, sender())
  }
}

Then when testing the actor, you can override the getHelloMessage implementation to do whatever you want.

class ToBeTestedActorTest extends TestKit(ActorSystem("toBeTested") with .... {
  trait MyToBeTested extends ToBeTested {
    // do something predictable for testing or defer to a TestProbe which you can
    // either define globally in the test class or provide one in a constructor.
    override def getHelloMessage(msg: String, replyTarget: ActorRef): String = ??? 
  }

  val toBeTestedActor = TestActorRef(Probe(new ToBeTestedActor with MyToBeTested))

  // ... (test cases)
}

In Java you can do pretty much the same thing. Since Java 8 you can provide default method implementations in interfaces, which you can override in a sub-interface for testing. Another way would be to subclass the actor in your test to override some methods to provide predictable behaviour.

// An easy unit testable interface
public interface ToBeTested {

  public ActorRef self();

  default public void getHelloMessage(String msg, ActorRef replyTarget) {
    replyTarget.tell(String.format("Hello %s", msg), self());
  }
}

public class ToBeTestedActor extends UntypedActor implements ToBeTested {

  // self() already implemented by Actor class

  @Override
  public void onReceive(Object message) throws Exception {

    if (message instanceof String) {
        getHelloMessage((String)message, getSender());
    }
  }
}

public class ToBeTestedActorTest {

  @Test
  public void test() throws Exception {
    ActorSystem system = ActorSystem.create();

    TestActorRef<Actor> testActorRef = TestActorRef.create(system, Props.create(TestActor.class));

    Future<Object> response = Patterns.ask(testActorRef, "World", 1000);
    assertThat(response.isCompleted(), is(true));
    assertThat(Await.result(response, Duration.Zero()), is("Test"));
  }

  // Override interface when using Java 8
  interface DummyToBeTested extends ToBeTested {
    @Override
    default void getHelloMessage(String msg, ActorRef replyTarget) {
        assertThat(msg, is("World"));
        replyTarget.tell("Test", self());
    }
  }

  // extend ToBeTestedActor with dummy interface
  static class TestActor extends ToBeTestedActor implements DummyToBeTested {}

  // Or (pre Java 8) extend the ToBeTestedActor directly 
  //    static class TestActor extends ToBeTestedActor {
  //        @Override
  //        public void getHelloMessage(String msg, ActorRef replyTarget) {
  //            replyTarget.tell("Test", self());
  //        }
  //    }
}
like image 28
Joost den Boer Avatar answered Sep 28 '22 00:09

Joost den Boer