Wednesday, January 30, 2013

Dependency Injection and Leaky Abstractions

Over the years I've generally made use of three basic methods of dependency injection...
  • Constructor Injection
    public class Widget
    {
        private WidgetDependency _dependency;
    
        public Widget(WidgetDependency dependency)
        {
            _dependency = dependency;
        }
    }
    
  • Setter Injection
    public class Widget
    {
        public WidgetDependency Dependency { get; set; }
    }
    
  • Late-Bound Injection
    public class Widget
    {
        public WidgetDependency Dependency
        {
            get
            {
                return ServiceLocatorObject.Resolve<widgetdependency>();
            }
        }
    }
    
Equally, over the years, I've unilaterally preferred the third option. Though I've never really given it a whole lot of thought or attempted to articulate why. It was just a preference. To some (and indeed often to attendees of my What Is Dependency Injection presentation) it can certainly seem like the more complex choice. Sure, it does present a little more code and a little more round-about way if getting what you're looking for, but it carries benefits. And I've recently found a compelling benefit that previously had just sort of tagged along for the ride without explicitly being known to me.

I started thinking about it a couple days ago when I answered a question on Stack Overflow. While answering the question I have to admit that I may not have fully understood what was being asked. But now I think I do. Looking back, it seems to me now that the communications failure between the asker and myself was specifically in regard to the above dependency injection options.

The asker was most likely supplying his dependencies by means of either constructor injection or setter injection. And, since I generally stay clear of those methods, it just didn't occur to me. Instead, again, I prefer the Common Service Locator as a pattern where a single object, bootstrapped at application start, is responsible for, well, locating services. Or dependencies, as it were.

The concern about which he was asking was how to write unit tests without first knowing the implementation of the code being tested. He's seen that Test Driven Development is preferred throughout the industry but couldn't reconcile how one can effectively write tests without knowing some details about the implementation. Specifically with regards to dependencies. And, furthermore, as the implementation changes and the dependencies change, he asserts that the unit tests would have to change as well.

He's right. Because he's using one of my non-preferred methods of dependency injection. And therein lies the problem. Let's assume for the moment that he's using constructor injection. In the example, then, he ends up with an object like this:

public class MyMathObject
{
    public MyMathObject(MyOtherClass1 dependency)
    {
        // implementation details
    }

    public int Add(int addend, int augend)
    {
        // implementation details
    }
}

Looks clean enough, right? But there's a problem. There's a leaky abstraction. His assertion was that, prior to implementing the class, the only known footprint is this:

public class MyMathObject
{
    public int Add(int addend, int augend)
    {
        // implementation details
    }
}

And that it's the implementation details which change the footprint to the previous one with the injectable dependencies. Thus, one ends up in a cycle. Define the object, write tests, implement the object (thus changing its footprint), re-write the tests to match, make updates to the object later in the software life cycle (maybe further changing its footprint), re-write the tests again to match, and so on.

He's right, that is a vicious cycle. And it's all stemming from that leaky abstraction. The problem isn't in the tests or in Test Driven Development at all. The problem is in a flaw in the design of the object. That constructor is making implementation details externally visible. It's not only allowing but requiring that all external neighbors to it know about its implementation details on some level in order to supply those dependencies. And, of course, as those details and dependencies change then all neighbors must also change.

This is tight coupling. If a change to Object A requires that Object B must also change then Object A and Object B are coupled around that feature. In this case those changes are internal implementation details, and so this coupling is of course wrong.

And this is precisely that subtle reasoning I've had behind my preferred method of dependency injection which has never explicitly occurred to me before. With something like Common Service Locator (or, more commonly for me, a home-grown implementation of the same pattern), the dependency injection within the object instead looks more like this:

public class MyMathObject
{
    public int Add(int addend, int augend)
    {
        var dependency = ServiceLocatorObject.Resolve<widgetdependency>();
        // more implementation details
    }
}

Externally to the object there is no knowledge of the dependency. The coupling is eliminated. So things like tests (or any other neighbors to this object anywhere in the system) don't need to know anything about its implementation details, just as it should be.

4 comments:

  1. Won't you have to change the test code regardless of the DI method being used? The symptoms will just change - with constructor injection, if a new dependency is added, the unit test will fail to compile; with setter and common service locator, you'll likely hit a NullReferenceException at runtime. So with constructor/setter you have to add a new mock, and with Common Service Locator you have to wire up the new dependency in your unit test bootstrap code.

    I'd argue that the your preferred option is *less* testable because it has a concrete dependency on the static ServiceLocatorObject. Using constructor or setter injection just requires a mock of the dependency. The coupling has not been eliminated, in fact it's greater - MyMathObject is coupled to ServiceLocatorObject *and* widgetdependency, as opposed to just widgetdependency.

    ReplyDelete
    Replies
    1. There's an application-level bootstrap that takes place, yes. I imagine this is to be expected, since a test suite is nothing more than another application instance.

      I can take this a step further and set up my dependency injection container to be configuration-driven as to which dependencies it uses (mocks, production, some other test version such as an XML-based repository instead of a SQL-based one, etc.). That would allow the same static service locator object to be used by the same domain objects in the same context at all times. The application's config file is what points to the mocks or not.

      So from the perspective of the domain objects, the tests, and everything else there is no difference in any of the configurations. Unit tests become re-usable as integration tests for all available integrations (all dependency implementations).

      Remember that the service locator object is really just a static-passthru to a dependency itself... The IoC container. That container is also switched out by the application instance at will. It's trivial to turn that static-passthru object dependency into a factory method call to allow more inversion:

      var dependency = ServiceLocatorFactory.Current.Resolve();

      Delete
    2. Everything you're describing can be accomplished using constructor/setter injection, without the side-effect of requiring the unit tests to be aware of the service locator. No matter how you slice it, if the module/method you're testng uses a dependency, your tests are going to have to supply it. I'd argue that constructor injection is the best way for modules to express what they depend on. I'd also argue that a *unit* test suite is *not* another application instance - we should be able to create unit test methods that stand up in complete isolation. For integration testing, sure, it makes sense to have a bootstrapper and test interdependencies.

      Going back to your original point, you're saying the guy was having trouble because his test-targets had extra dependencies. My point is that moving the dependency to an internal servicelocator call doesn't remove the dependency, it just makes it harder to discover why things aren't working when somehing breaks. In your final example, you're saying tests don't need to know anything about the implementation destails of MyMathObject.Add, but they do - if they don't wire up the widgetdependency, the test will fail.

      Delete
    3. Why isn't a test suite an application instance? What is a test runner if not an application? Something is running the tests, executing the code. That something is, for all intents and purposes, an application. Capable of its own configuration like any other. Maybe that configuration is a config file, maybe it's a bootstrap method in the test suite, that part is immaterial.

      By advertising in the constructor what the dependencies are, the object is still advertising implementation details. The test suite can bootstrap the overall domain dependency graph (with mocks) without having to know or care which dependencies each object individually needs. The concern of wiring up the dependencies would belong squarely within the service locator implementation, all an application instance (or test suite) has to do is initialize it on startup. (For an application it would generally happen once, for a test suite it would happen before each isolated test.)

      Those advertised dependencies in the constructor would put the tests in a position that they need to be changed any time the internal implementation is changed, even if the external behavior is not changed. (A private refactoring.) If changing the internal implementation of a class forces other code outside that class to change, it's tightly coupled.

      Delete