left-icon

Akka.NET Succinctly®
by Zoran Maksimovic

Previous
Chapter

of
A
A
A

CHAPTER 11

Unit Testing Akka.NET

Unit Testing Akka.NET


It’s hard to imagine modern application development without the possibility of performing unit tests, or testing in general. Automated tests are not only an important part, but also an essential part of any application development cycle.

As testing actors is a bit different from testing any other piece of object-oriented software that we’re used to, Akka.NET comes with a dedicated module, Akka.TestKit, for supporting tests at different levels.

Akka.TestKit is the fundamental library to be used in order to perform tests. In addition to this, there are different libraries built on top of it in order to support several other unit-testing frameworks, such as Xunit, NUnit, and Visual Studio tests.

Akka.NET testing libraries

Figure 33: Akka.NET testing libraries

Including these libraries is as easy as installing them in the usual way from the NuGet package manager console.

Code Listing 62

PM> Install-Package Akka.TestKit

PM> Install-Package Akka.TestKit.NUnit

The TestKit class is the base for all of the unit tests, and all the unit test classes should inherit from it. TestKit class is actually implemented by the various libraries supporting the specific testing framework—as the real base class we have the Akka.TestKit.TestKitBase class, which is implemented in the Akka.TestKit library.

We would usually never inherit directly from the Akka.TestKit.TestKitBase, since there would be quite a few things to be implemented, and indeed, every library specific to the testing framework in use (for example, NUnit) would implement the plumbing needed to work with the actor system hiding this complexity from us.

Hierarchy of the TestKit classes

Figure 34: Hierarchy of the TestKit classes

The most basic unit test would look as follows:

Code Listing 62: Test skeleton

[TestFixture]

public class CalculatorActorTest: Akka.TestKit.NUnit.TestKit

{

    [Test]

    public void SomeTest()

    {

        /* test goes here */

    }

}

If you are not familiar with [TestFixture] and [Test], these are the two attributes used to mark tests with NUnit, which we are going to use in the examples. You might use your favorite testing framework equally; the concepts are usually very similar.

We can divide the actor unit testing into four categories:

  • Direct: Unit tests are written in the same way as normal classes. However, this method doesn’t use any actor system, and therefore is limited in the sense that when performing direct tests, we are completely avoiding the message passing, which is the reason why we use the actor system. On the other hand, this kind of testing allows us to inspect the state of the actor, so it’s very useful.
  • Unit Testing: Unit tests are written by using the ActorSystem, and performed against one actor in particular.
  • Integration Testing: We can use this technique when there is a need to test multiple message passing between actors.
  • Mixed Mode: Unit tests that use the unit testing/integration testing with the help of the direct testing.

Direct testing

Performing direct testing is pretty straightforward, and not any different from what we usually do when testing any other kind of class. There is one thing in particular that we need to be careful about, which is to mark all of the message handlers as public, so that they’re accessible to the unit test code. At first, this might sound like a security issue, but in reality, when actors are used within the actor system, these public methods are never accessed outside the actor itself.

Let’s use our SongPerformanceActor as the actor to be tested. If you recall, this actor was used to increase the count of how many times a song has been played. So, this actor has a state, and this state can be tested. As a reminder, this actor looks like the following. Please note that we changed the SongPerformanceCounter property to public, together with the IncreaseSongCounter method.

Code Listing 63: SongPerformanceActor used for direct testing

public class SongPerformanceActor : ReceiveActor

{

    public Dictionary<string, int> SongPeformanceCounter;

    public SongPerformanceActor()

    {

        SongPeformanceCounter = new Dictionary<string, int>();

        Receive<PlaySongMessage>(m => IncreaseSongCounter(m));

    }

    public void IncreaseSongCounter(PlaySongMessage m)

    {

        var counter = 1;

        if (SongPeformanceCounter.ContainsKey(m.Song))

        {

            counter = SongPeformanceCounter[m.Song]++;

        }

        else

        {

            SongPeformanceCounter.Add(m.Song, counter);

        }

    }

}

We changed the private/protected methods to public so that they can be accessed by the unit test.

Our first test to check that the SongPeformanceCounter doesn’t contain any entry upon an actor’s creation would look as follows:

Code Listing 64: Actor starts with an empty state

[TestFixture]

public class SongPerformanceActorTest: Akka.TestKit.NUnit.TestKit

{

    [Test]

    public void ShouldStartWithAnEmptyState()

    {

        var actor = new SongPerformanceActor();

        Assert.False(actor.SongPeformanceCounter.Any());

    }

}

Nothing really difficult here. We instantiate a new actor, and simply check that there are no entries in the SongPerformanceCounter property.

The next thing to test would be increasing the count after playing a song. To achieve this, we would simply use the IncreaseSongCounter method and assert the result once again.

Let’s add a new test method and invoke the IncreaseSongCounter method. We are asserting that the counter was set to 1 after “playing” the song once. Needless to say, this test passes.

Code Listing 65: Check whether the statistics are increased by one

[Test]

public void ShouldIncreaseSongByOne()

{

    var actor = new SongPerformanceActor();

    var songMessage = new PlaySongMessage("Bohemian Rhapsody", "John");

    actor.IncreaseSongCounter(songMessage);

    Assert.True(actor.SongPeformanceCounter[songMessage.Song] == 1);

}

This is very direct, and not at all different from what we already know about unit testing.

UnitTesting

A unit test is about testing the actor using the messaging involved by using the actor system. Now things are getting more complicated, and we need to introduce few concepts first.

When executing tests, the base TestKit class will create a test actor system in the background, referred to as Sys. Being an actor system, it will allow us to instantiate new actors, as we have seen in previous chapters, by using ActorOf. There is an alternative to this method without Sys.ActorOf, which is to use ActorOf<T>. Let’s see a quick example of the ActorOf usage.

Code Listing 66: Not compiling ActorOf test

[Test]

public void ShouldIncreaseSongByOne()

{

    //var actor = base.Sys.ActorOf(new Actor.Props(typeof(SongPerformanceActor)));

    IActorRef actor = ActorOf<SongPerformanceActor>();

    var songMessage = new PlaySongMessage("Bohemian Rhapsody", "John");

    actor.Tell(songMessage);

    Assert.True(actor.SongPeformanceCounter[songMessage.Song] == 1);

}

However, we can quickly notice that the SongPerformanceCounter class attribute is not any more visible! Unfortunately, this test cannot be performed, so we need to use the ActorOfAsTestActorRef<T> method. This is a wrapper method, and it has the UnderlyingActor attribute, which represents the underlying actor’s instance, so that we can access its internal properties.

Code Listing 67: Passing unit test that uses ActorOfAsTestActorRef

[Test]

public void ShouldIncreaseSongByOne()

{

    TestActorRef<SongPerformanceActor> actor =                     

                                         ActorOfAsTestActorRef<SongPerformanceActor>();

    var songMessage = new PlaySongMessage("Bohemian Rhapsody", "John");

    actor.Tell(songMessage);

    Assert.True(actor.UnderlyingActor.SongPeformanceCounter[songMessage.Song] == 1);

}

Testing an actor’s message replies

In this section, we will find out how to test a situation where the actor will reply with a message: this is to demonstrate how to test the messages sent back by using the Sender.Tell(message) method. In order to do this, we are going to slightly adapt our actor’s behavior so that, after increasing the counter, it will send back the message saying that the counter was increased.

The only change is to the IncreaseSongCounter method, highlighted in bold. In addition, we are also specifying the CounterIncreasedMessage message that will be resent.

Code Listing 68: Addition of the Sender.Tell method

public void IncreaseSongCounter(PlaySongMessage m)

{

    var counter = 1;

    if (SongPeformanceCounter.ContainsKey(m.Song))

    {

        counter = SongPeformanceCounter[m.Song]++;

    }

    else

    {

        SongPeformanceCounter.Add(m.Song, counter);

    }   

    Sender.Tell(new CounterIncreasedMessage(m.Song, counter));

}

public class CounterIncreasedMessage

{

    public string Song;

    public long Count;

    public CounterIncreased(string song, long count)

    {

        this.Song = song;

        this.Count = count;

    }

}

Let’s see how can we prove that this message is sent back to the sender.

Akka.TestKit offers a set of assert helper methods such as ExpectMsg, ExpectMsgFrom, and ExpectNoMsg, in order to test that the message is sent back from an actor. In our test example, we can see the ExpectMsg and the commented-out ExpectMsgFrom in action.

Code Listing 70: Testing return message with ExpectMsg()

[Test]

public void ShouldResendCounterIncreasedMessage()

{

    TestActorRef<SongPerformanceActor> actor =

                                        ActorOfAsTestActorRef<SongPerformanceActor>();

    var songMessage = new PlaySongMessage("Bohemian Rhapsody", "John");

    actor.Tell(songMessage);

    /*

     * specify the actor explicitly

     * var replyMessage = ExpectMsgFrom<CounterIncreasedMessage>(actor);

     */

    var counter = ExpectMsg<CounterIncreasedMessage>(TimeSpan.FromSeconds(5));

    Assert.That(counter.Song == "Bohemian Rhapsody");

    Assert.That(counter.Count == 1);

}

It’s worth noticing that the ExpectMsg returns an instance of the CounterIncreasedMessage for further inspection and assertion. By default, the method will wait for three seconds, but this can be overridden as needed (TimeSpan.FromSeconds(5)).

ExpectNoMsg will test the fact that no messages are received, which would work fine in our previous example, before we decided to send back the message.

ExpectMsgFrom will test that the message comes from a specific actor.

Integration testing

In this section we are going to see how to check the parent-child relationship between two actors. If you recall, with MusicPlayerCoordinatorActor and MusicPlayerActor, the first will receive the message and create a new instance of the MusicPlayerActor for every user that plays music. The new child actor will be given the name of the user itself.

Here is the simplified version of the actors:

Code Listing 69: Simplified version of the MusicPlayerActor for testing

public class MusicPlayerActor : ReceiveActor

{

    protected PlaySongMessage CurrentSong;

    public MusicPlayerActor()

    {

        Receive<PlaySongMessage>(m => PlaySong(m));

    }

    private void PlaySong(PlaySongMessage message)

    {

        CurrentSong = message;

        Console.WriteLine(

                $"{CurrentSong.User} is currently listening to '{CurrentSong.Song}'");

    }

}

The corresponding MusicPlayerCoordinatorActor follows:

Code Listing 70: Simplified version of the MusicPlayerCoordinatorActor for testing

public class MusicPlayerCoordinatorActor : ReceiveActor

{

    protected Dictionary<string, IActorRef> MusicPlayerActors;

    public MusicPlayerCoordinatorActor()

    {

        MusicPlayerActors = new Dictionary<string, IActorRef>();

        Receive<PlaySongMessage>(message => PlaySong(message));

    }

    private void PlaySong(PlaySongMessage message)

    {

        var musicPlayerActor = EnsureMusicPlayerActorExists(message.User);

        musicPlayerActor.Tell(message);

    }

    private IActorRef EnsureMusicPlayerActorExists(string user)

    {

        IActorRef musicPlayerActorRef;

        if (MusicPlayerActors.ContainsKey(user))

        {

            musicPlayerActorRef = MusicPlayerActors[user];

        }

        else

        {

            musicPlayerActorRef = Context.ActorOf<MusicPlayerActor>(user);

            MusicPlayerActors.Add(user, musicPlayerActorRef);

        }

        return musicPlayerActorRef;

    }

}

One aspect of the testing we might tackle is the creation of the child actor. How can we ensure that the child actor is created after we send a message to the MusicPlayerCoordinator actor? One way is to use the ActorSelection as follows:

Code Listing 71: Testing that the child actor is created

[Test]

public void ShouldInstantiateANewChildActor()

{

    TestActorRef<MusicPlayerCoordinatorActor> actor =

        ActorOfAsTestActorRef(() => new MusicPlayerCoordinatorActor(), "Coordinator");

    var songMessage = new PlaySongMessage("Bohemian Rhapsody", "John");

    actor.Tell(songMessage);

    IActorRef child = this.Sys.ActorSelection("/user/Coordinator/John")

        .ResolveOne(TimeSpan.FromSeconds(5))

        .Result;

         

    Assert.That(child != null);

}

Since we know that every actor has a Path and can have a name specified, we are specifying the coordinator’s actor name explicitly, as it is going to help us to define the final path. When creating the MusicPlayerCoordinatorActor, we are telling the test system to call it Coordinator.

We also already know that when the new child actor gets created, it will have the name of the user playing the song, so we know which Path the child actor will have upon instantiation, and we are going to use this information. In our case, this is John.

In order to figure out whether the child got created, we use the ActorSelection object, specifying the direct path to the child actor and checking for its existence. The resolution of the actor will take up to five seconds, and if the actor is not created in this timeframe, the test will throw an error and fail.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.