Wednesday, 22 June 2011

Unit testing your unity IOC wiring

I'm using Unity for the IOC container at the moment which is great, injecting in dependencies to the controller makes the dev cycle so quick, without it TDDing would be significantly more difficult. we can unit test everything including the controllers, but you all know that.

There is a small downside to using unity, in that you only find out about problems to the wiring when you run your app, or your acceptance tests, and then you need to analyse the error and figure out what went wrong.

So i thought wouldn't it be nice to have a unit test to test your unity configuration :-)
Well here it is

using NUnit.Framework;
using System.Reflection;
using System.Web.Mvc;

namespace Tests.Unit.Consumer
{
    [TestFixture]
    public class ProductionWiringDefinitionTest
    {
        [Test]
        public void ProductionWiringIsComplete()
        {
            IWiringDefinition wiringDefinition = new ConsumerProductionWiringDefinition();
            WiringDefinitionAssertions.AssertWiringDefinitionAllDependenciesResolved(wiringDefinition);
        }
    }
  
    public static class WiringDefinitionAssertions
    {
        public static void AssertWiringDefinitionAllDependenciesResolved(IWiringDefinition wiringDefinition)
        {
            var unityDependencyResolver = UnityDependencyResolver.Instance;
            unityDependencyResolver.Configure(wiringDefinition);
            DependencyResolver.SetResolver(unityDependencyResolver);            
            
            var containerFieldInfo = unityDependencyResolver.GetType().GetField("container", BindingFlags.NonPublic | BindingFlags.Instance);
            var container = (Microsoft.Practices.Unity.UnityContainer) containerFieldInfo.GetValue(unityDependencyResolver);

            foreach (var registration in container.Registrations)
            {
                unityDependencyResolver.GetService(registration.RegisteredType);                   
                unityDependencyResolver.GetService(registration.MappedToType);                   
            }
        }
    }  
}

The lines that do the magic are 30 and 31 inside the for loop, basically we try and instantiate everything that unity knows about, if any dependencies are missing you will get a nice unit test failure and a very good error message telling you exactly what is wrong.

You may have to change this example a little as it uses our abstraction of IWiringDefinition because we have multiple sites all doing the same thing but with different wiring.

using Microsoft.Practices.Unity;
namespace Framework.Ioc
{
    public interface IWiringDefinition
    {
        void Configure(IUnityContainer container);
    }
}

And an example of our wiring definition

using Diagnostics;
using Microsoft.Practices.Unity;

namespace Consumer.Application
{
    public class ConsumerProductionWiringDefinition : IWiringDefinition
    {
        public void Configure(IUnityContainer container)
        {
            RegisterViewModelMappers(container);
            RegisterDatabaseContext(container);
            RegisterControllers(container);
        }

        private static void RegisterDatabaseContext(IUnityContainer container)
        {
            container.RegisterType<IDatabaseManager, DatabaseManager>();
            container.RegisterType<DatabaseManager>(new InjectionConstructor(new DbConnectionString().ForConsumer));
        }

        private static void RegisterControllers(IUnityContainer container)
        {
            container.RegisterType<DiagnosticsController>(new InjectionConstructor(new ServerDiagnosticsController()));
        }

        private static void RegisterViewModelMappers(IUnityContainer container)
        {
            container.RegisterType<ITopicWithArticleSummariesMapper, TopicWithArticleSummariesMapper>();
            container.RegisterType<IArticleSummaryMapper, ArticleSummaryMapper>();
            container.RegisterType<IToolsPageViewModelMapper, ToolsPageViewModelMapper>();
            container.RegisterType<IToolViewModelMapper, ToolViewModelMapper>();
        }
    }
}

Like i said this example above is not entirely complete, you will need a little more than this to get your wiring working, but its a good introduction to actually testing your wiring.

Good luck.