Posts /

Testing composition root using Castle Windsor

26 Feb 2018

Why?

Composition is not the first thing people strive to white-box test. As almost everything depends on composition root working, mistakes made in component wiring manifest themselves quickly. Programmers tend to run their application before they push their changes, so a constructor dependency missing from registration won’t even make it to testing environments.

Nevertheless.

Test 1: Does it compose?

This is the standard snippet used to check Castle Windsor wiring:

[TestMethod]
void RootComposes()
{
    WindsorContainer container = CompositionRoot.Get();

    var host = (IDiagnosticsHost)container.Kernel.GetSubSystem(SubSystemConstants.DiagnosticsKey);
    var misconfigurations = host.GetDiagnostic<IPotentiallyMisconfiguredComponentsDiagnostic>().Inspect();

    if (misconfigurations.Any())
    {
        var diagnosticOutput = new StringBuilder();

        foreach (IExposeDependencyInfo problem in misconfigurations)
        {
            problem.ObtainDependencyDetails(new DependencyInspector(diagnosticOutput));
        }

        Assert.Fail(diagnosticOutput.ToString());
    }
}

Such check will notice situations like

class ImapMailReceiver : IMailReceiver
{
    public ImapMailReceiver(IImapConfiguration imapConfiguration) { }
}

static class CompositionRoot
{
    public static WindsorContainer Get()
    {
        var container = new WindsorContainer();

        container.Register(
            Component.For<IMailReceiver>().ImplementedBy<ImapMailReceiver>().LifestyleSingleton()
        );

        return container;
    }
}

i.e., situations when registered implementation cannot get its dependencies fulfilled (as IImapConfiguration is not even mentioned in CompositionRoot.Get). The error is nicely descriptive:

'ImapMailReceiver' is waiting for the following dependencies:
- Service 'IImapConfiguration' which was not registered.

Implicit dependencies in wiring logic

This works as long as you don’t depend on your environment too much in CompositionRoot.Get(). An implicit dependency on appsettings such as this will probably kill your test:

static class CompositionRoot
{
    public static WindsorContainer Get()
    {
        var container = new WindsorContainer();

        container.Register(
            Component
                .For<IMailReceiver>()
                .ImplementedBy<ImapMailReceiver>()
                .DependsOn(Dependency.OnValue("imapAddress", ConfigurationManager.AppSettings["ImapAddress"]))
                .LifestyleSingleton()
        );

        return container;
    }
}

Let’s be thorough here. The standard way of doing dependency injection have three distinct phases:

It’s widely recognized that the constructor logic (the resolve time logic) should not be heavy, both in terms of computing capacity required, and in terms of dependencies on the rest of the system.

I think we should hold such standards even more stringently at registration time: making wiring implicitly dependent on a working database connection or domain-level configuration presence is worse than letting constructor block on network communication. There are practical reasons – we don’t want to loose all functionality with just part of the necessary setup missing – but there are also architectural concerns: it’s just not composition root’s job to care about settings that should be owned by component internals. It’s a detail from composition’s point of view whether there is a prefix or not in some network configuration key.

This is important for us because we want to run CompositionRoot.Get() in a unit test, as we saw in the code example. Such unit test won’t have our usual environment set up and we can’t mock anything without

It’s not at all difficult to move all the logic from wiring and constructors into the actual run time:

What I’m trying to argue here is that there is a larger cause for refactoring configuration, database and network dependencies outside of wiring code. In this sense, you should feel good about having a pretext for finally doing it.

Test 2: Are lifetimes matching?

Curious person might ask whether the line host.GetDiagnostic<IPotentiallyMisconfiguredComponentsDiagnostic>().Inspect() can be modified to get access to diagnostics other than the one looking for PotentiallyMisconfiguredComponents.

It can! IPotentialLifestyleMismatchesDiagnostic looks through the dependency tree for components with long lifestyle depending on components with short ones: cases like singleton components with dependencies on transient components, or singletons depending on PerWebRequests.

Such dependencies result in an unintuitive behavior: singleton will hold its instance of “transient” dependency for long time, breaking potential assumptions on transient service longevity.

[TestMethod]
void RootDoesNotMismatchLifestyles()
{
    var container = CompositionRoot.Get();

    var host = (IDiagnosticsHost)container.Kernel.GetSubSystem(SubSystemConstants.DiagnosticsKey);
    var misconfigurations = host.GetDiagnostic<IPotentialLifestyleMismatchesDiagnostic>().Inspect();

    if (misconfigurations.Any())
    {
        var diagnosticOutput = new StringBuilder();

        diagnosticOutput.AppendLine("Following dependency chains have inconsistent accessibility:" + Environment.NewLine);

        foreach (var misconfiguration in misconfigurations)
        {
            diagnosticOutput.AppendLine(
                string.Join(
                    " -> ",
                    misconfiguration
                        .Where(m => m.ComponentModel.Services.Any())
                        .Select(m => $"{m.ComponentModel.Services.First().Name} ({m.ComponentModel.LifestyleType})")));
        }

        Assert.Fail(diagnosticOutput.ToString());
    }
}

You might run into a case when this kind of dependency is desirable. Adding list of exceptions into the test is straightforward then.

Test 3: Are there any useless registrations?

One of the disadvantages of dependency injection is the reduced ability to use static reasoning over the codebase. We can see interfaces mentioned in constructor arguments, we can see methods called over these interfaces, but we can’t “go to definition” just as easily as we were used to in the non-polymorphic world of yesterday.

Specially, when we remove the last dependency to some service, we won’t see “0 references” in Visual Studio, because there is still this basic mention – registration in composition root.

The question arise: can we test that there are registrations that won’t be used? The answer is sure, as long as

After this is settled, a simple graph traversal starting from our root (to-be-resolved) services will do:

[TestMethod]
public void ServicesAreUsed()
{
    var coreServices = new[] {
        "Namespace.OrderController",
        "Namespace.CustomerController",
        "Namespace.ReportController"
    };

    var container = CompositionRoot.Get();

    var services = container.Kernel.GraphNodes.Where(n => n is ComponentModel).Cast<ComponentModel>();
    var usefulServices = new HashSet<ComponentModel>();
    var servicesToProcess = new Stack<ComponentModel>(
        services.Where(service =>
            coreServices.Any(coreService =>
                service.ComponentName.Name == coreService)));

    while (servicesToProcess.TryPop(out var service))
    {
        usefulServices.Add(service);
        
        foreach (var subservice in service.Dependents.Where(n => n is ComponentModel).Cast<ComponentModel>())
        {
            servicesToProcess.Push(subservice);
        }
    }

    var unusefulServices = new HashSet<ComponentModel>(services);
    unusefulServices.ExceptWith(usefulServices);

    Assert.AreEqual(
        0, unusefulServices.Count,
        "Following services are registered and are not needed: " + string.Join(", ", unusefulServices));
}

Test 4: Are we using composition root as a service locator?

We can’t stop people from doing things like this:

public void ProcessPayment(...)
{
    // ...

    var ccProcessor = CompositionRoot.Get().Resolve<ICreditCardProcessor>();
    ccProcessor.Process(...);

    // ...
}

What can be spotted though are slightly more civilized transgressions that do not shamefully try to call static (gasp!) methods to get what they need, but that are still similar in the harmful core approach of service locator: making your service dependent on an unspecified whole world of IWindsorContainer instead of descriptive and statically checkable interfaces of actual subservices.

public class PaymentProcessingService
{
    private readonly IWindsorContainer _container;

    public PaymentProcessingService(IWindsorContainer container)
    {
        _container = container;
    }

    public void ProcessPayment(...)
    {
        // ...

        var ccProcessor = _container.Resolve<ICreditCardProcessor>();
        ccProcessor.Process(...);

        // ...
    }
}

Now that the container knows it’s being abused, IUsingContainerAsServiceLocatorDiagnostic diagnostic can be applied:

[TestMethod]
public void ServiceLocatorNotPresentTest()
{
    var container = CompositionRoot.Get();

    var host = (IDiagnosticsHost)container.Kernel.GetSubSystem(SubSystemConstants.DiagnosticsKey);
    var locators = host.GetDiagnostic<IUsingContainerAsServiceLocatorDiagnostic>().Inspect();

    if (locators.Any())
    {
        var diagnosticOutput = new StringBuilder();

        diagnosticOutput.AppendLine("Following components looks suspiciously like a service locator:" + Environment.NewLine);
        foreach (var locator in locators)
        {
            diagnosticOutput.AppendLine(locator.ToString());
        }

        Assert.Fail(diagnosticOutput.ToString());
    }
}

It’s useful to notice that the implementation of the diagnostic just checks against a list of too-general services well-behaved service should not depend upon. It’s a simple mechanism.

Test 5: Are root services resolvable?

Before the end, let’s return to our earlier considerations about stages of dependency injection. Because we succeeded at testing wiring, we might proceed towards checking resolvability (i.e., testing the second stage) of the root services of our application. We are going to use the very same “service roots” we talked about in the third part.

[DataTestMethod]
[DataRow(typeof(OrderController))]
[DataRow(typeof(CustomerConstroller))]
[DataRow(typeof(ReportController))]
public void ControllersCanBeResolved(Type t)
{
    CompositionRoot.Get().Resolve(t);
}

This is a slightly more integration-y way for doing checks and leads us towards white-box testing guided by live composition roots. We still should not need live database or some customization of the root for succeeding: even in the second stage, in the constructor stage, we should not need configuration/database/network to be actually present.

Because of wiring checks from the beginning of the article, we already know that dependencies are registered properly. So, in the end, doing this second-stage testing is a way to enforce constructor cleanness across our product.

Conclusion

Taking proper care here has a potential to make a whole class of boring issues disappear. Yay!

All the examples are present in the repository I prepared. Feel free to fork it, clone it, play with it.


Lukáš Lánský (e-mail)
Return to the list of posts


Share: Twitter Facebook Google+