Как проверить функциональность Akka Actor, издеваясь над одним или несколькими методами в нем

Мне интересно узнать, как протестировать функциональность Akka Actor, , высмеивая некоторые методы (замените реализацию реализатора реального объекта/актора, посмеявшись над ним) в Actor.

Я использую akka.testkit.TestActorRef;

Также: я пытался использовать SpyingProducer, но неясно, как его использовать. (например, если бы я создал актера внутри его реализации, он был бы таким же, как у меня сейчас). Результат поиска Google об этом не очень подробный.

Я использую powemockito и java. Но это не имеет значения. Мне было бы интересно узнать how to do it in principle с любым языком с любой структурой

(так что если вы не знаете, как работают power/mockito, просто предоставляйте свои код.. (пожалуйста) или полное представление о том, как вы это сделаете с помощью своих инструментов, которые вы знаете.)

Итак, скажем, у нас есть актер для тестирования:

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;
    }

}

И в нашем тесте мы хотим заменить getHelloMessage() на возврат чего-то еще.

Это моя попытка:

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");
        }
    }
}

Результат:

org.junit.ComparisonFailure: 
Expected :nothing
Actual   :Hello, Bob

Ответ 1

Akka имеет класс AutoPilot, который в основном является общим макетом для участников, с возможностью отвечать на сообщения и утверждать, что сообщения отправлены. http://doc.akka.io/docs/akka/snapshot/java/testing.html

Вот пример java для этой страницы. Вы создаете зонд, устанавливаете автопилот, который может отвечать на сообщения, и получайте ActorRef от него, который вы можете заменить для своего реального актера.

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();
}};

Ответ 2

У меня нет опыта использования Akka с Java, но я думаю, что решение для этого, которое я использую в Scala, также может применяться к Java. Нет необходимости вообще что-то издеваться. В Java mocking иногда полезно для тестирования, но мой личный опыт/мнение в том, что всякий раз, когда вам нужен PowerMock, вы делаете что-то неправильно.

Вот как я пытаюсь проверить с помощью Akka:

В Scala я использую черту (aka interface), в которой определены методы актера.

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

Таким образом, эта функциональность может быть протестирована очень просто. Для настоящего актера я пытаюсь реализовать только метод приема.

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

Затем при тестировании актера вы можете переопределить реализацию getHelloMessage, чтобы делать все, что хотите.

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)
}

В Java вы можете делать почти то же самое. Начиная с Java 8 вы можете предоставить реализацию методов по умолчанию в интерфейсах, которые вы можете переопределить в суб-интерфейсе для тестирования. Другой способ заключается в подклассе актера в вашем тесте, чтобы переопределить некоторые методы для обеспечения предсказуемого поведения.

// 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());
  //        }
  //    }
}

Ответ 3

Поэтому я, вероятно, не понимаю вопроса, но вы, вероятно, не хотите издеваться над актером, поскольку целью насмешки является замена чего-то вроде дао тестовой копией, которая имеет ожидания вызова - актер на самом деле не подгоняйте счет как нечто, чем вы расширяете, а не зависимость - насмешка действительно применима только к реальным зависимостям.

TestActorRef специально предоставляет вам доступ к основному игроку - в большинстве обычных случаев вы можете отправлять сообщения только актеру и не ссылаться на что-либо прямо на нем. TestActoRef устраняет это ограничение, позволяя вам получить доступ к реальному истинному расширению Actor, а не только к ActorRef, который вы можете только! или? против (отправить или спросить).

Я - разработчик scala, поэтому проницательность, как мы надеемся, агностика. Я не знаю java api специально, но это не имеет значения.

Моя рекомендация - получить реальный объект Actor через референт actor и просто протестировать метод или выяснить, каким образом получить тестовое покрытие через реальные сообщения.

Ответ 4

To mock an actor is easier through the TestActorRef. You can use this code :

    static ActorSystem system = ActorSystem.create();
    static Props propsSome = Props.create(MockedResultActor.class);

    TestActorRef<MockedResultActor> refMockedResultActor= TestActorRef.create(
                    system, propsSome, "testA");

    // Mocking an actor class and returning our reference actor
    PowerMockito.mockStatic(ClassToBeMocked.class);
    Mockito.when(ClassToBeMocked.getToBeMockedMethod())
                    .thenReturn(refMockedResultActor);

Note: ClassToBeMocked--Its a class you want to mock.
MockedResultActor -- Its a class you want to return after mocking.
This can be run using JunitTest after implementing basic configuration of mocking in your class. Code given here is specific to akka actor in java only.