Introduction

Myself and my colleagues found actors inside a hierarchy difficult to unit test when we began working on our first Akka production code base. I would like to first discuss why we found it difficult before presenting my solution.

The problem

When unit testing, we wanted to mock out all children and parent actors, and for integration tests we wanted to mock out only some of the children and parents.

This Petabridge blog post describes how to unit test a parent-child relationship with the following example:

public class ChildActor : ReceiveActor
{
    public ChildActor()
    {
        ReceiveAny(o => Sender.Tell("hello!"));
    }
}

public class ParentActor : ReceiveActor
{
    public ParentActor()
    {
        var child = Context.ActorOf(Props.Create(() => new ChildActor()));
        ReceiveAny(o => child.Forward(o));
    }
}

[TestFixture]
public class ParentGreeterSpecs : TestKit
{
    [Test]
    public void Parent_should_create_child()
    {
        // verify child has been created by sending parent a message
        // that is forwarded to child, and which child replies to sender with
        var parentProps = Props.Create(() => new ParentActor());
        var parent = ActorOfAsTestActorRef<ParentActor>(parentProps, TestActor);
        parent.Tell("this should be forwarded to the child");
        ExpectMsg("hello!");
    }
}

Although this example seems fine, if the ChildActor had its own children, then this test could potentially run code in those children and every single actor underneath them. Take the following actor hierarchy (where actor C has an interface injected into it rather than having its own children):

Actor hierarchy

Using the Petabridge method to test actor D, the actors being tested look like this:

Actor hierarchy highlighted where testing actor D

It would be much better to mock out the children and just test actor D like this:

Actor hierarchy highlighted where testing actor D with mocks

The Petabridge blog post also describes how to replace your actors with mock versions, such as the `BlackHoleActor` using the following example:

public IdentityManagerActor(IActorRef authenticationActor)
{
    _authenticator = authenticationActor;

    Receive<CreateUser>(create =>
    {
        var senderClosure = Sender;

        // this actor needs it create user request to be authenticated
        // within 2 seconds or this operation times out & cancels
        // the Task returned by Ask<>
        _authenticator.Ask<UserResult>(create, TimeSpan.FromSeconds(2))
            .ContinueWith(tr =>
            {
                if (tr.IsCanceled || tr.IsFaulted)
                    return new UserResult(false);

                return tr.Result;
            }).PipeTo(senderClosure);
    });
}

// other code omitted for brevity...

[Test]
public void IdentityManagerActor_should_fail_create_user_on_timeout()
{
    // the BlackHoleActor will NEVER respond to any message sent to it
    // which will force the CreateUser request to time out
    var blackhole = Sys.ActorOf(BlackHoleActor.Props);
    var identity = Sys.ActorOf(Props.Create(() => new IdentityManagerActor(blackhole)));

    identity.Tell(new CreateUser());
    var result = ExpectMsg<UserResult>().Successful;
    Assert.False(result);
}

Although again this example seems fine, it results in losing the actor hierarchy. If you create two root actors from your ActorSystem and then inject one into another, then the actors are siblings rather than parent and child.

About the solution

If you want to maintain the actor hierarchy then you must create children inside the parent. This is where DI comes to the rescue. Akka makes it easy to modify how child actors are resolved. This is done by implementing an interface called DependencyResolver. The interface takes in the Type of actor you want to create, and then performs some logic to create it. Dependency resolvers don’t have to create the actor type that is requested, they can return any actor type. This makes it possible to create a simple dependency resolver that always returns mock actors regardless of the type is asked for.

The TestProbe is a great testing class in Akka that you can use as a mock actor. They allow you to assert if they have received a certain message. You can also modify their behaviour using an object called AutoPilot. This makes it a perfect class to return from a DependencyResolver.

Child actors are created asynchronously. This means that when running a test to assert that an actor has created a child, then it is very possible that your assertion could be ran before the child is created. This kind of async issue can crop up when running unit tests on slow build servers. Akka has a class called TestLatch that allows you to block a thread until an event has happened a certain amount of times. This makes it a perfect mechanism for waiting until the required number of children have been resolved.

The solution

My unit testing solution is a custom DependencyResolver that replaces all resolved children with TestProbe instances and that uses a TestLatch to ensure that the children have been created before running any assertions.

My NuGet package wraps all this up in a way that is super easy to consume. The resolver keeps track of all the test probes so you can access them once your SUT (System Under Test) has been created.

Below is an example test class that checks if an actor creates the correct type, and gives it the correct name:

using Akka.Actor;
using Akka.DI.Core;
using Akka.TestKit;
using Akka.TestKit.Xunit2;
using FluentAssertions;
using Xunit;

namespace ConnelHooley.AkkaTestingHelpers.DI.MediumTests.TestProbeResolverTests.Examples
{
    public class Examples1 : TestKit
    {
        public class ChildActor : ReceiveActor { }

        public class ParentActor : ReceiveActor
        {
            public ParentActor()
            {
                var child1 = Context.ActorOf(Context.DI().Props<ChildActor>(), "child-actor-1");
                var child2 = Context.ActorOf(Context.DI().Props<ChildActor>(), "child-actor-2");
                child1.Tell("hello actor 1");
                child2.Tell(42);
            }
        }

        [Fact]
        public void ParentActor_Constructor_CreatesChildWithCorrectTypeAndName()
        {
            //arrange
            TestProbeResolver resolver = TestProbeResolverSettings
                .Empty
                .CreateResolver(this);
            
            //act
            TestActorRef<ParentActor> sut = resolver.CreateSut<ParentActor>(2);
            
            //assert
            resolver
                .ResolvedType(sut, "child-actor-1")
                .Should().Be<ChildActor>();
        }

        [Fact]
        public void ParentActor_Constructor_SendsChildCorrectMessage()
        {
            //arrange
            TestProbeResolver resolver = TestProbeResolverSettings
                .Empty
                .CreateResolver(this);

            //act
            TestActorRef<ParentActor> sut = resolver.CreateSut<ParentActor>(2);

            //assert
            resolver
                .ResolvedTestProbe(sut, "child-actor-1")
                .ExpectMsg("hello actor 1");
        }
    }
}

Let’s walk through this example:

  • Firstly, you create a settings object. The settings object contains the custom handlers for each of your children. If you do not want your actor’s children to reply to any messages then create an empty settings object. Once you have a settings object you can construct the resolver from the settings object by passing in a TestKit instance.
  • Then you can create an actor. If the actor has a parameterless constructor then you can simply enter the number of children you expect it to create. The resolver then waits for the specified number of children to be resolved before returning a TestActorRef.
  • You can query the resolver for either of the following based on the child’s expected name:
    • The resolved TestProbe.
    • The type of actor the parent asked for.

Below is an example that configures the child actor to reply to messages, and then asserts that a mock is given the replied message:

using Akka.Actor;
using Akka.DI.Core;
using Akka.TestKit;
using Akka.TestKit.Xunit2;
using Moq;
using Xunit;

namespace ConnelHooley.AkkaTestingHelpers.DI.MediumTests.TestProbeResolverTests.Examples
{
    public class Examples2 : TestKit
    {
        public class ChildActor : ReceiveActor
        {
            public class ModifiedSave
            {
                public string Value { get; }

                public ModifiedSave(string value)
                {
                    Value = value;
                }
            }
        }

        public interface IRepository
        {
            void Save(string value);
        }

        public class ParentActor : ReceiveActor
        {
            public ParentActor(IRepository repo)
            {
                var child = Context.ActorOf(Context.DI().Props<ChildActor>(), "child-actor-1");
                Receive<Save>(s => child.Tell(s));
                Receive<ChildActor.ModifiedSave>(s => repo.Save(s.Value));
            }

            public class Save
            {
                public string Value { get; }

                public Save(string value)
                {
                    Value = value;
                }
            }
        }
        
        [Fact]
        public void ParentActor_ReceiveSaveMessage_StoresModifiedSaveMessageFromChildInRepo()
        {
            //arrange
            TestProbeResolver resolver = TestProbeResolverSettings
                .Empty
                .RegisterHandler<ChildActor, ParentActor.Save>(s => new ChildActor.ModifiedSave(s.Value.ToUpper()))
                .CreateResolver(this);
            Mock<IRepository> repoMock = new Mock<IRepository>();
            Props props = Props.Create(() => new ParentActor(repoMock.Object));
            TestActorRef<ParentActor> sut = resolver.CreateSut<ParentActor>(props, 1);

            //act
            sut.Tell(new ParentActor.Save("hello world"));

            //assert
            AwaitAssert(() => repoMock.Verify(repo => repo.Save("HELLO WORLD"), Times.Once));
        }
    }
}

Let’s walk through this example:

  • The ParentActor in this example doesn’t have a parameterless constructor, so a props object must be given to the CreateSut method. This props object is used to inject the mock instances into the actor. When the actor is ran in production a genuine implementations would be provided.
  • When creating the TestProbeResolverSettings object you can register handlers. A handler is a method that is ran when a particular type of actor receives a particular type of message. This example registers a handler that runs whenever a ChildActor receives a ParentActor.Save message. The handler returns an upper case ChildActor.ModifiedSave message. The returned message is then sent back to the sender actor.

The following example demonstrates how to check that an actor sends a message to its parent or supervisor.

using Akka.Actor;
using Akka.TestKit;
using Akka.TestKit.Xunit2;
using Xunit;

namespace ConnelHooley.AkkaTestingHelpers.DI.MediumTests.TestProbeResolverTests.Examples
{
    public class Examples3 : TestKit
    {
        public class DummyActor : ReceiveActor
        {
            public DummyActor()
            {
                Receive<string>(s =>
                {
                    Context.Parent.Tell(s.ToUpper());
                });
            }
        }

        [Fact]
        public void DummyActor_ReceiveStringMessage_SendsUpperCaseStringMessageToSupervisor()
        {
            //arrange
            TestProbeResolver resolver = TestProbeResolverSettings
                .Empty
                .CreateResolver(this);
            TestActorRef<DummyActor> sut = resolver.CreateSut<DummyActor>(0);

            //act
            sut.Tell("hello world");

            //assert
            resolver.Supervisor.ExpectMsg("HELLO WORLD");
        }
    }
}

Let’s walk through this example:

  • When the resolver creates the sut actor, it creates it with a TestProbe as its parent, which you can access using the Supervisor property. This ensures you know the sut actor is sending messages to its parent and not the sender.

Get it on NuGet

You can install it on NuGet using the following command or usual methods:

Install-Package ConnelHooley.AkkaTestingHelpers.DI

 View the source code on GitHub

What’s next

I am looking to migrate from .NET framework to .NET standard. I will also provide documentation on GitHub. I have also started a resolver for integration tests so will update when that is finished.