Skip to main content

Unit testing Akka.NET actors and MEF

As you maybe realized from the title, I will talk about Akka.NET, a .NET port of famous Akka framework, combined with MEF dependency injection library, and how you can use mocks (I will use Moq to leverage mocking power) to unit-test Akka.Net actors.

So, let's say we have some actor class with external dependency and this actor needs to be tested.
using System.Composition;

using Akka.Actor;

namespace AkkaMefTest
{
    [Export]
    public class MyActor : ReceiveActor
    {
        [Import]
        public IDependency Dependency { get; set; }

        public MyActor()
        {
            this.Receive<TestMessage>(message => this.ProcessMessage(message));
        }

        private void ProcessMessage(TestMessage message)
        {
            // process message in some way here
            // ...
            this.Dependency.DoSomething();
        }

        public class TestMessage
        {
            public string Message { get; private set; }

            public TestMessage(string message)
            {
                this.Message = message;
            }
        }
    }
}

As you can see, the actor is pretty simple - it handles only one message and while doing this, calls Dependency.DoSomething() method. The IDependency interface itself contains only one method:
namespace AkkaMefTest
{
    public interface IDependency
    {
        void DoSomething();
    }
}

Ok. Now when we have functional code, we need to figure out how to unit-test it.
Let's write down things we will need to do in order to write unit test for our actor:

  1. Testing framework. Akka.NET supports a lot of famous testing frameworks. I personally prefer NUnit.
  2. Mock external dependency - this is really easy actually. Moq library will help us to do it.
  3. Because we are using dependency injection (MEF) we need find a way how to inject our mock into actor. This will covered lately.
Keeping this 3 things in mind, let's create a test for our actor.

using Akka.Actor;
using Akka.DI.Core;
using Akka.TestKit.NUnit;

using Moq;

using NUnit.Framework;

namespace AkkaMefTest
{
    [TestFixture]
    public class Tests : TestKit
    {
        protected IActorRef MyActor { get; private set; }

        protected Mock<IDependency> DependencyMock { get; private set; }

        [SetUp]
        public void Initialize()
        {
            this.DependencyMock = new Mock<IDependency>();

            this.MyActor = this.Sys.ActorOf(this.Sys.DI().Props<MyActor>(), "test");
        }

        [Test]
        public void Send_test_message_and_check_dependency_was_called()
        {
            // arrange
            var finished = this.CreateTestBarrier(count: 2);
            // act
            this.MyActor.Tell(new MyActor.TestMessage("This is test message."));
            // assert
            finished.Await();
            this.DependencyMock.Verify(dep => dep.DoSomething(), Times.Once);
        }
    }
}

This code is almost correct. Why almost? Despite that it will compile successfully, it won't work and test will fail because we are not injecting DependencyMock into MyActor instance. However, we can easily oversome this by creating special dependency resolver.
using System;
using System.Collections.Concurrent;
using System.Composition;
using System.Linq;
using System.Reflection;

using Akka.Actor;
using Akka.DI.Core;
using Akka.TestKit.NUnit;

using Moq;

namespace AkkaMefTest
{
    public class TestDependencyResolver : IDependencyResolver
    {
        private readonly ActorSystem _system;

        private readonly TestKit _test;

        private readonly ConcurrentDictionary<string, Type> _typeCache;

        public TestDependencyResolver(ActorSystem system, TestKit test)
        {
            this._test = test;
            this._typeCache = new ConcurrentDictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
            this._system = system;
            this._system.AddDependencyResolver(this);
        }

        public Props Create<TActor>() where TActor : ActorBase
        {
            return this._system.GetExtension<DIExt>().Props(typeof(TActor));
        }

        public Func<ActorBase> CreateActorFactory(Type actorType)
        {
            return () => this.InitializeActorWithImports(actorType);
        }

        public Type GetType(string actorName)
        {
            this._typeCache.TryAdd(actorName, actorName.GetTypeValue());

            return this._typeCache[actorName];
        }

        public void Release(ActorBase actor)
        {
            // do nothing
        }

        private ActorBase InitializeActorWithImports(Type actorType)
        {
            PropertyInfo[] mockProperties =
                this._test.GetType()
                    .GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance)
                    .Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(Mock<>))
                    .ToArray();
            PropertyInfo[] importProperties =
                actorType.GetProperties(BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.SetProperty | BindingFlags.Instance)
                         .Where(p => p.GetCustomAttribute<ImportAttribute>() != null)
                         .ToArray();
            object actor = Activator.CreateInstance(actorType);
            foreach (PropertyInfo importProperty in importProperties)
            {
                PropertyInfo property = importProperty;
                PropertyInfo mockProperty = mockProperties.SingleOrDefault(
                    p => p.PropertyType.GetGenericArguments().Single() == property.PropertyType);
                if (null == mockProperty)
                {
                    throw new Exception(string.Format("Can't find mock for import [{0}]", importProperty.Name));
                }

                object importValue = ((Mock)mockProperty.GetValue(this._test)).Object;
                importProperty.SetValue(actor, importValue);
            }

            return (ActorBase)actor;
        }
    }
}

This dependency resolver searches for mock instances in test class and injects them into instance of an actor under test. Quite simple, isn't it? Let's finish our test class by creating dependency resolver's instance.
        ...
        [SetUp]
        public void Initialize()
        {
            this.DependencyMock = new Mock<IDependency>();

            var dependencyResolver = new TestDependencyResolver(this.Sys, this);
            this.MyActor = this.Sys.ActorOf(this.Sys.DI().Props<MyActor>(), "test");
        }

Compile and run our test - works like a charm!

Comments

Popular posts from this blog