scala 如何通过模拟 Akka Actor 中的一个或多个方法来测试它的功能

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16715949/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 05:19:10  来源:igfitidea点击:

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

javascalamockingakkaactor

提问by ses

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.

我很想知道如何通过模拟 Actor 中的一些方法用模拟的方法替换真实对象/演员的方法实现)来测试 Akka Actor 的功能。

I use akka.testkit.TestActorRef;

我用akka.testkit.TestActorRef;

Also: I tried to use SpyingProducerbut 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.

另外:我尝试使用SpyingProducer但不清楚如何使用它。(就像我在它的实现中创建 actor 一样,它与我现在的一样)。谷歌搜索结果不是很详细

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

我使用powemockitojava。但是这没关系。我有兴趣了解how to do it in principle任何具有任何框架的语言

(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.)

(因此,如果您不知道 power/mockito 是如何工作的,只需提供您的代码..(请)或关于您将如何使用您知道的工具进行操作的完整想法。)

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

因此,假设我们有一个要测试的 Actor:

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.

在我们的测试中,我们想用getHelloMessage()返回其他东西来代替。

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

回答by Douglas Rapp

Akka has a class AutoPilotthat 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

Akka 有一个类AutoPilot,它基本上是演员的通用模拟,能够响应消息并断言消息已发送。 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 ActorReffrom it that you can substitute in for your real actor.

这是该页面的 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();
}};

回答by Joost den Boer

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.

我没有在 Java 中使用 Akka 的经验,但我想我在 Scala 中使用的解决方案也适用于 Java。根本没有必要嘲笑任何东西。在 Java 中,模拟有时对测试很有用,但我个人的经验/意见是,每当您需要 PowerMock 时,您就做错了。

Here's how I try to test using Akka:

这是我尝试使用 Akka 进行测试的方法:

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

在 Scala 中,我使用了一个 trait(又名接口),其中定义了 actor 方法。

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.

然后在测试 actor 时,您可以覆盖 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)
}

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.

在 Java 中,您几乎可以做同样的事情。从 Java 8 开始,您可以在接口中提供默认方法实现,您可以在子接口中覆盖以进行测试。另一种方法是将测试中的actor 子类化以覆盖某些方法以提供可预测的行为。

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

回答by JasonG

So I'm probably not understanding the question but you probably don't want to mock an actor as the purpose of mocking is to replace something like a dao with a test copy that has expectations of invocation - actor doesn't really fit the bill as it's something you extend rather than a dependency - mocking really only applies to real dependencies.

所以我可能不理解这个问题,但你可能不想嘲笑演员,因为嘲笑的目的是用期望调用的测试副本替换像 dao 这样的东西——演员并不真正符合要求因为它是您扩展的东西而不是依赖项 - 模拟实际上仅适用于真正的依赖项。

TestActorRef specifically gives you access to the underlying actor - in most normal circumstances you can only send messages to an actor and not invoke anything directly on it. TestActoRef removes this limitation by allowing you to access your real true extension of Actor instead of just the ActorRef that you can only ! or ? against (send or ask).

TestActorRef 专门为您提供了对底层参与者的访问权——在大多数正常情况下,您只能向参与者发送消息,而不能直接在其上调用任何内容。TestActoRef 通过允许您访问您真正真正的 Actor 扩展,而不仅仅是您只能访问的 ActorRef 来消除此限制!或者 ?反对(发送或询问)。

I'm a scala dev so the insight is hopefully agnostic. I don't know the java api specifically but it shouldn't matter.

我是一个 Scala 开发者,所以我的见解是不可知的。我不知道具体的 java api,但这应该无关紧要。

My recommendation is to get the real Actor object via actor ref and just test the method or figure out some way to get test coverage through real messages.

我的建议是通过actor ref 获取真正的Actor 对象,然后测试该方法或找出某种方法通过真实消息获得测试覆盖率。

回答by Parnik Kumar Gaurav

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

通过 TestActorRef 可以更轻松地模拟演员。您可以使用此代码:

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.

注意:ClassToBeMocked--它是您要模拟的类。MockedResultActor——它是一个你想在模拟后返回的类。这可以在您的类中实现模拟的基本配置后使用 JunitTest 运行。此处给出的代码仅适用于 java 中的 akka actor。