Monday, March 2, 2015

Testing Ninject container

I've found really useful to test our IoC container to make sure that all the components that are registered can be resolved.

This is the way I usually test my Ninject container

The model

so let's say that we have the following model:

public interface ICommand { }

public interface ICommandHandler<in T> where T : ICommand
{
    void Handle(T command);
}

public class DeploySqlScriptsUsingAGlobalTransactionCommand : ICommand
{
    public DeploySqlScriptsUsingAGlobalTransactionCommand(IEnumerable<string> scripts)
    {
        Scripts = scripts;
    }

    public IEnumerable<string> Scripts { get; private set; }
}

public class DeploySqlScriptsUsingAGlobalTransactionCommandHandler :
    ICommandHandler<DeploySqlScriptsUsingAGlobalTransactionCommand>
{
    private readonly IScriptDeployerService _scriptDeployerService;

    public DeploySqlScriptsUsingAGlobalTransactionCommandHandler(IScriptDeployerService scriptDeployerService)
    {
        _scriptDeployerService = scriptDeployerService;
    }

    public void Handle(DeploySqlScriptsUsingAGlobalTransactionCommand command)
    {
        //shalalalalal
        command.Scripts.ToList().ForEach(_scriptDeployerService.Deploy);
        Console.WriteLine("Scripts deployed");
    }
}

public interface IScriptDeployerService
{
    void Deploy(string script);
}

Note that we don't have a concrete implementation of IScriptDeployerService

The test

So we create this test to make sure that the container is valid:

[TestMethod]
public void it_should_resolve_all_registered_components_in_the_container()
{
    #region Binding container

    var kernel = new StandardKernel();

    kernel.Bind(config =>
    {
        config.FromThisAssembly()
            .SelectAllClasses()
            .BindAllInterfaces();
    });

    ServiceLocator.SetLocatorProvider(() => new NinjectServiceLocator(kernel));

    kernel.Bind<IServiceLocator>().ToConstant(ServiceLocator.Current); 

    #endregion

    #region Testing container

    var field = typeof (KernelBase).GetField("bindings",
        BindingFlags.Instance | BindingFlags.NonPublic);

    field.Should().NotBeNull();

    var bindingsMap = field.GetValue(kernel) as Multimap<Type, IBinding>;

    bindingsMap.Should().NotBeNull();
    bindingsMap.Should().NotBeEmpty();

    var locator = ServiceLocator.Current.GetInstance<IServiceLocator>();

    locator.Should().NotBeNull();

    bindingsMap.SelectMany(x => x.Value).ToList()
        .ForEach(binding => ServiceLocator.Current.Invoking(instance =>
        {
            var services = instance.GetAllInstances(binding.Service);

            services.Count().Should().BeGreaterOrEqualTo(1);

            if (services.Count() == 1)
            {
                instance.GetInstance(binding.Service);
            }
        }).ShouldNotThrow());

    #endregion
}

The error

When we run the test we receive the following error from Ninject:

Result Message: Did not expect any exception, but found Ninject.ActivationException with message "Error activating IScriptDeployerService No matching bindings are available, and the type is not self-bindable. Activation path: 2) Injection of dependency IScriptDeployerService into parameter scriptDeployerService of constructor of type DeploySqlScriptsUsingAGlobalTransactionCommandHandler 1) Request for ICommandHandler{DeploySqlScriptsUsingAGlobalTransactionCommand}

Very explicit

The fix

So now let's fix it:

Let's add the missing concrete implementation:

public class ScriptDeployerService : IScriptDeployerService
{
    public void Deploy(string script)
    {
        Console.WriteLine("Script deployed: {0}", script);
    }
}

The result

And now our test pass =) Awesome

No comments:

Post a Comment

If you found this post useful please leave your comments, I will be happy to hear from you.