In my last blog post I introduced the actor model using the Akka.NET framework. And since I like to write unit tests for my projects I was wondering how to test the few actors I have created even if there is not much logic inside them.
I believe that understanding how to write tests for a given technology early is important in order to avoid creating too much coupling. And also tests provide rapid feedback helping us to debug what we are creating, if you can debug quickly you can learn quickly.
When working with Akka.NET, you can use Akka.TestKit to write the tests for your actor system. Then there are several nuget packages depending on the testing framework you use.
Install-Package Akka.TestKit.VsTest
Install-Package Akka.TestKit.NUnit
Install-Package Akka.TestKit.Xunit
In my case I will use the VsTest package for MSTest.
Writing your first test
I will start by testing the behavior of the BlueActor, here is its definition:
public class BlueActor : ReceiveActor { private const string ActorName = "BlueActor"; private const ConsoleColor MessageColor = ConsoleColor.Blue; private int _counter = 0; protected override void PreStart() { base.PreStart(); Become(HandleString); } private void HandleString() { Receive<string>(s => { PrintMessage(s); _counter++; Sender.Tell(new MessageReceived(_counter)); }); } private void PrintMessage(string message) { Console.ForegroundColor = MessageColor; Console.WriteLine( "{0} on thread #{1}: {2}", ActorName, Thread.CurrentThread.ManagedThreadId, message); } }
As you can see, there is not much to test, I can’t really test that the actor is writing something on the Console. But I might be able to test that it replies to the sender with a message containing a counter.
In my example the sender was an instance of a GreenActor but in a testing context it does not have to be that way. Let’s use TestKit to write a test.
[TestClass] public class BlueActorTests : TestKit { [TestCleanup] public void Cleanup() { Shutdown(); } [TestMethod] public void BlueActor_Respond_With_Counter() { var actor = ActorOfAsTestActorRef<BlueActor>(); actor.Tell("test"); var answer = ExpectMsg<MessageReceived>(); Assert.AreEqual(1, answer.Counter); } }
The test class inherits from TestKit in order to access a set of functionalities allowing to test an actor. Since an actor “lives” inside an actor system in an asynchronous way, it would be a real pain to mock everything, TestKit does it for us.
I can now use the ActorOfAsTestActorRef<T> method in order to get an IActorRef wrapped in a specific instance dedicated to testing. In this context the message sender is the test class, I can then test that the actor replies with a MessageReceived instance. To do so I can use the ExpectMsg<T> method to do the check and to retrieve the message, if there is no message of this type the test fails.
It is also important to call the Shutdown method after running the tests in order to dispose of the actor system without risking to produce memory leak.
Testing another actor
I will now write a test for the YellowActor, see definition below.
public class YellowActor : UntypedActor { private const string ActorName = "YellowActor"; private const ConsoleColor MessageColor = ConsoleColor.Yellow; private IActorRef _greenActor; protected override void PreStart() { base.PreStart(); _greenActor = Context.ActorOf<GreenActor>(); } protected override void OnReceive(object message) { if (message is string) { var msg = message as string; PrintMessage(msg); _greenActor.Tell(msg); } } private void PrintMessage(string message) { Console.ForegroundColor = MessageColor; Console.WriteLine( "{0} on thread #{1}: {2}", ActorName, Thread.CurrentThread.ManagedThreadId, message); } }
There is no reply to check, this actor just transfer the message to another actor after printing it. I can still write a test if I want.
[TestClass] public class YellowActorTests : TestKit { [TestCleanup] public void Cleanup() { Shutdown(); } [TestMethod] public void YellowActor_Should_Not_Answer() { var actor = ActorOfAsTestActorRef<YellowActor>(); actor.Tell("test"); ExpectNoMsg(); } }
In this test I use the ExpectNoMsg method to verify that the actor does not answer. The test pass but I would like to test that the message is transferred to another actor. Unfortunately at the moment I cannot (or I did not find the way to do it) without changing the code of the actor.
The greenActor IActorRef is created by the actor itself and therefore is not a TestActorRef. To change this, I will inject the actor reference in the constructor, this way I will be able to use a testing reference when running tests. I add the following constructor to the YellowActor class:
public YellowActor(IActorRef greenActor) { _greenActor = greenActor; }
And I remove the PreStart method. At this point the code does not compile, I have to update the code creating the actor.
var yellowProps = Props.Create<YellowActor>(actorSystem.ActorOf<GreenActor>()); var yellowActor = actorSystem.ActorOf(yellowProps);
This is one of several way to inject dependencies to an actor. And I can now update my test to check that a message is sent.
[TestMethod] public void YellowActor_Should_Send_String_Message() { var props = Props.Create<YellowActor>(TestActor); var actor = ActorOfAsTestActorRef<YellowActor>(props); actor.Tell("test"); var message = ExpectMsg<string>(); StringAssert.Equals("test", message); }
I used the TestActor property available in the test class as dependency for the yellow actor. And now I am able to verify that a message is sent using the ExpectMsg method. If I had let the ExpectNoMsg method the test would have failed because the actor system in my test context has received a message.
This is the end of this small introduction to testing with Akka.TestKit for Akka.NET.
Wrapping up
I am definitely not an expert regarding Akka.NET and even less with actor testing, so if you find mistakes regarding my approach feel free to teach me. I wanted to introduce testing early in order to make me think deeper when using Akka.NET. From this experience I learned that the actor model works as a whole and it is something to consider in order to avoid making mistakes.
I am also glad to have been able to write some tests for my actors without too much pain using the tool provided by the community, big thank to them for the hard work.
I will continue my journey with this technology and I will share my thoughts with blog posts. There is much to discover and to learn.
See you next time!
thanx man, you’re the hero of the day :D
Finally I got my unit tests right. Btw it was the inheritance from TestKit which I couldn’t find in the documentation. Sometimes it is so easy…
LikeLike
Hello Floris,
Thank you for your comment, I’m glad if I was able to help you! And for sure the easiest bugs to fix are not always the quickest.
LikeLike