How to use TestKit in Akka.NET

2019-04-21 03:36发布

问题:

I'm trying to test my Akka.NET actors, but are having some trouble with the TestKit and understanding how it works.

Since there is no official documentation for unit testing in Akka.NET yet, I've explored the Akka.NET repo for example code, but the examples used there doesn't work for me.

The tests I've used for reference is ReceiveActorTests.cs and ReceiveActorTests_Become.cs, since those are close to the scenario I'm trying to test in my app.

Here's some dummy code:

Given this actor

public class Greeter : ReceiveActor
{
    public Greeter()
    {
        NotGreeted();
    }

    private void NotGreeted()
    {
        Receive<Greeting>(msg => Handle(msg));
    }

    private void Greeted()
    {
        Receive<Farewell>(msg => Handle(msg));
    }

    private void Handle(Greeting msg)
    {
        if (msg.Message == "hello")
        {
            Become(Greeted);
        }
    }

    private void Handle(Farewell msg)
    {
        if (msg.Message == "bye bye")
        {
            Become(NotGreeted);
        }
    }
}

I want to test that it Receives the Greeting and Farewell messages correctly, and enters the Become-states correctly. Looking at the ReceiveActorTests_Become.cs tests, an actor is created by

var system = ActorSystem.Create("test");
var actor = system.ActorOf<BecomeActor>("become");

and a message is sent and asserted by

actor.Tell(message, TestActor);
ExpectMsg(message);

However, when I try this approach to instantiating an actor, and many others based on TestKit methods (see below), I keep getting the samme failed test error:

Xunit.Sdk.TrueExceptionFailed: Timeout 00:00:03 while waiting for a message of type ConsoleApplication1.Greeting 
Expected: True
Actual:   False

This is my test:

public class XUnit_GreeterTests : TestKit
{
    [Fact]
    public void BecomesGreeted()
    {
        //var system = ActorSystem.Create("test-system"); // Timeout error
        //var actor = system.ActorOf<Greeter>("greeter"); // Timeout error
        //var actor = ActorOfAsTestActorRef<Greeter>("greeter"); // Timeout error
        //var actor = ActorOf(() => new Greeter(), "greeter"); // Timeout error
        //var actor = Sys.ActorOf<Greeter>("greeter"); // Timeout error
        //var actor = Sys.ActorOf(Props.Create<Greeter>(), "greeter"); // Timeout error
        var actor = CreateTestActor("greeter"); // Works, but doesn't test my Greeter actor, but rather creates a generic TestActor (as I understand it)

        var message = new Greeting("hello");

        actor.Tell(message, TestActor);

        ExpectMsg(message);
    }
}

I've also tried moving the ExpectMsg line above the actor.Tell line (since it made more sense to Expect something before you act on it and rather verify the expectation after), but this also results in the Timeout error.

I've tried with both NUnit and XUnit TestKits.

There's probably something really basic I've overlooked.

回答1:

TestKit is used for more behavioral testing to verify if your actors work as expected in context of the whole actor system. This is more like black box testing - you don't reach the insides of an actor directly. Instead it's better to focus on behavior such as given signal A and actor behavior B it should emit message C to another actor D.

In your example problem with the Greeter actor is that it's mute - while it can receive some inputs, it doesn't do anything in result. From the perspective of the whole system it could be dead and nobody would care.

Using other example - given following actor:

public class Greeter : ReceiveActor
{
    public Greeter()
    {
        Receive<Greet>(greet =>
        {
            // when message arrives, we publish it on the event stream 
            // and send response back to sender
            Context.System.EventStream.Publish(greet.Who + " sends greetings");
            Sender.Tell(new GreetBack(Self.Path.Name));
        });
    }
}

Let's create an example test spec:

public class GreeterSpec : TestKit
{
    private IActorRef greeter;

    public GreeterSpec() : base()
    {
        greeter = Sys.ActorOf<Greeter>("TestGreeter");
    }

    [Fact]
    public void Greeter_should_GreetBack_when_Greeted()
    {
        // set test actor as message sender
        greeter.Tell(new Greet("John Snow"), TestActor);
        // ExpectMsg tracks messages received by TestActors
        ExpectMsg<GreetBack>(msg => msg.Who == "TestGreeter");
    }

    [Fact]
    public void Greeter_should_broadcast_incoming_greetings()
    {
        // create test probe and subscribe it to the event bus
        var subscriber = CreateTestProbe();
        Sys.EventStream.Subscribe(subscriber.Ref, typeof (string));

        greeter.Tell(new Greet("John Snow"), TestActor);

        // check if subscriber received a message
        subscriber.ExpectMsg<string>("John Snow sends greetings");
    }
}

As you can see, here I don't check the internal state of the actor. Instead I'm observing how it reacts on signals I send to it and verify if it's an expected result.



回答2:

You don't and shouldn't create your own ActorSystem as part of any Akka.TestKit test. You can use the built-in Sys property within any of your tests and you'll get access to the same ActorSystem that is used by the TestKit itself.

So you should do something like this:

var actor = Sys.ActorOf<BecomeActor>("become");

The reason why this is important: having the TestActor and your BecomeActor in the same ActorSystem is necessary in order for them to send messages to each other, unless you're using Akka.Remote. Otherwise TestActor can't receive any messages and your ExpectMsg calls will time out.

EDIT: the entire test actor system is now torn down between unit tests.

EDIT 2: See the detailed guide we wrote to the Akka.NET TestKit for a longer explanation.