问题
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.
来源:https://stackoverflow.com/questions/30730706/how-to-use-testkit-in-akka-net