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

后端 未结 4 1651
旧巷少年郎
旧巷少年郎 2021-02-04 10:34

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 moc

4条回答
  •  小鲜肉
    小鲜肉 (楼主)
    2021-02-04 11:08

    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 testActorRef = TestActorRef.create(system, Props.create(TestActor.class));
    
        Future 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());
      //        }
      //    }
    }
    
        

    提交回复
    热议问题