Chapter 3 - Dependency Injection

Dependency Injection Primer

Although not the primary subject of this discussion is it important to understand the concept of dependency injection and how it relates to effective software testing. If you are already a dependency injection ninja then you can skip this section and move along.

Not surprisingly Wikipedia provides a concise description of Dependency Injection:

“Dependency injection is a software design pattern that allows a choice of component to be made at run-time rather than compile time”

Importantly Dependency Injection (DI) is a design pattern and does not refer to any specific implementation of library, although there are several DI systems available which provide a faster path to implementing a DI oriented solution (e.g. the Spring framework for Java).

Dependency injection & mocks

To understand why adopting a dependency injection approach to software architecture is important.

Consider the following code example:

public void doSomething(String data) {

    DatabaseWriter writer = new DatabaseWriter();

    if(data != null) {

        Emailer emailer = new Emailer();

        try {
            Serializer serializer = new Serializer();
            Object value = serializer.deserialize(data);
            writer.writeValueToDatabase(value);
            emailer.sendConfirmationEmail();
        }
        catch (Exception e) {
            writer.logErrorToDatabase(e);
            emailer.sendErrorEmail();
        }
    }
    else {
        writer.logErrorToDatabase("No data");
    }
}

We can immediately see that the above method depends on three external systems (classes):

  • DatabaseWriter
  • Serializer
  • Emailer

This means we can’t test our “doSomething” method without implicitly executing all the code behind both all the dependent objects: DatabaseWriter, Serializer and Emailer. These dependent objects may themselves have further dependencies which would then need to be satisfied for any test to have a hope of passing.

A common solution to this is to use mock objects. These are implementations of the dependent classes (DatabaseWriter, Serializer and Emailer) which appear the same (i.e. they have the same signature) but are actually skeleton implementations that do not execute any real code. In dynamic languages like as Python or JavaScript the type of a class can be changed at runtime. This means if we were to rewrite the above code in a language like JavaScript, like so:

function doSomething(data) {
    var writer = new DatabaseWriter();

    if(data !== null) {

        var emailer = new Emailer();

        try {
            var serializer = new Serializer();
            var value = serializer.deserialize(data);
            writer.writeValueToDatabase(value);
            emailer.sendConfirmationEmail();
        }
        catch (error) {
            writer.logErrorToDatabase(error);
            emailer.sendErrorEmail();
        }
    }
    else {
        writer.logErrorToDatabase();
    }
}

We could easily mock out our dependencies by simply changing the declaration of these in our tests:

function DatabaseWriter() {
    DatabaseWriter.prototype.writeValueToDatabase = function(data) {
        // I’m a mock!
    }
}

However in compiled languages like Java this runtime manipulation of type definitions is much more difficult. Changing the definition of what a DatabaseWriter is at runtime would require a deep and low level understanding of (in the case of Java) how the current classloader has interpreted the byte code found in the classpath, and an ability to change this. Neither of these is trivial, nor indeed in some cases even possible.

The alternative is to create extended versions of these classes and override all public methods. So we could create a “MockDataBaseWriter” class that does not actually write to a database but we then need a way to inject this mock into the object we are testing so it uses our mock rather than the real DatabaseWriter.

Unfortunately the very first line of our “doSomething” method prevents this:

public void doSomething(String data) {
    DatabaseWriter writer = new DatabaseWriter();
    //...

The allocation of the DatabaseWriter instance via the new operator inside the method means it is simply not possible to change the behavior of this class.

Ideally we want to inject these dependencies into the method. To achieve this there are a couple of different approaches. The following is a quite lengthy discussion on how to inject dependencies in a compiled language. If you’re already familiar with this as an approach you can skip past it.

Alternative 1 - Method parameters

public void doSomething(
        String data,
        DatabaseWriter writer,
        Emailer emailer,
        Serializer serializer) {

    if(data != null) {
        try {
            Object value = serializer.deserialize(data);
            writer.writeValueToDatabase(value);
            emailer.sendConfirmationEmail();
        }
        catch (Exception e) {
            writer.logErrorToDatabase(e);
            emailer.sendErrorEmail();
        }
    }
    else {
        writer.logErrorToDatabase("No data");
    }
}

In this approach the code under test (the “guts” of the method) does not have any external dependencies so during testing we could do something like this:

DatabaseWriter mockWriter = <Create Mock>;
Emailer mockEmailer = <Create Mock>;
Serializer mockSerializer = <Create Mock>;
String data = "test";

// Orchestrate mocks...

setupMocks(mockWriter, mockEmailer, mockSerializer);

doSomething(data, mockWriter, mockEmailer, mockSerializer);

verifyMocks(mockWriter, mockEmailer, mockSerializer);

This is fine for a small method where the dependencies are few and easily obtained but can run into issues if there are many dependencies which lead to overly verbose method signatures. The advantage of this approach is that it is self documenting. The method signature informs the developer of the dependencies required by the method (assuming null arguments are not acceptable)

Alternative 2 - Object level fields (most common)

public class DoesSomething {
    private DatabaseWriter writer;
    private Emailer emailer;
    private Serializer serializer;

    public void setWriter(DatabaseWriter writer) {
        this.writer = writer;
    }

    public void setEmailer(Emailer emailer) {
        this.emailer = emailer;
    }

    public void setSerializer(Serializer serializer) {
        this.serializer = serializer;
    }

    public void doSomething(String data) {
        if(data != null) {
            try {
                Object value = serializer.deserialize(data);
                writer.writeValueToDatabase(value);
                emailer.sendConfirmationEmail();
            }
            catch (Exception e) {
                writer.logErrorToDatabase(e);
                emailer.sendErrorEmail();
            }
        }
        else {
            writer.logErrorToDatabase("No data");
        }
    }

}

In this alternative the dependencies are set at a class (instance) level which means our method signature remains concise but has the by-product of being prone to error as it may be unclear to the developer which instance-level dependencies are required to satisfy the requirements of the method (which can lead to annoying “Null Pointer” failures).

Alternative 3 - Constructor injection and/or allocation

This is where the dependent objects are either nominated as constructor arguments, or they are created during the constructor execution. For example:

public class DoesSomething {
    private DatabaseWriter writer;
    private Emailer emailer;
    private Serializer serializer;

    public DoesSomething(
            DatabaseWriter writer,
            Emailer emailer,
            Serializer serializer) {

        this.writer = writer;
        this.emailer = emailer;
        this.serializer = serializer;
    }
}

OR

public class DoesSomething {
    private DatabaseWriter writer;
    private Emailer emailer;
    private Serializer serializer;

    public DoesSomething() {
        this.writer = new DatabaseWriter();
        this.emailer = new Emailer();
        this.serializer = new Serializer();
    }
}

There are some advantages to the former of these two examples (constructor injection), namely that it’s clear to users of the class what is needed in order for the object to operate. That is, the dependencies are well defined, fixed and immovable. The disadvantage of this approach is that either you don’t provide a default constructor at all in which case calling classes need to know how to create the necessary dependencies (which may be a good thing) and makes later refactoring more complicated. If a change to the constructor arguments is necessary the effects of this change can be far more widespread. This also has the effect of making the object difficult to create through reflection or other runtime inspection methods as well as making the object far less portable as serialization/deserialization becomes more difficult. Although would also be true for any object that has dependencies regardless of their source.

Conversely dependencies that are allocated in the constructor mean that callers do not need to know how to bootstrap the object correctly, but it also means that the side effects to creating an object are hidden from the caller. This intra-constructor dependency allocation can quickly lead to problems as the chain of constructions increases and means that all objects that “might” be a dependency “must” have a parameterless constructor.

Consider the following example:

public class A {
    private B b;

    public A() {
        this.b = new B();
    }
}

public class B {
    private C c;

    public B() {
        this.c = new C();
    }

    public void someMethod() {
        // Does something
    }
}

public class C {
    private DatabaseWriter writer;

    public C() {
        this.writer = new DatabaseWriter();
    }
}

public class DatabaseWriter {
    public DatabaseWriter() {
        this.connectToDatabase();
    }

    private void connectToDatabase() {
        //  Does something
    }
}

In the above example a programmer wanting an instance of “A” would have no immediate appreciation that simply creating an instance of “A” would result in a connection being made to a database via a sequence of chained constructor executions. Additionally their allocation of instance A would result in four additional allocations.

If “A” is allocated frequently this may have an adverse impact on performance.

Now the immediate response may be to say, “well don’t connect to the database in the constructor!”, which sounds sensible but the programmer who created the “DatabaseWriter” class may have had good reason for this (or not!) but more importantly could not have known the chain of constructors that would precede his own and should be free to make decisions in the best interest of the system they are developing.

An additional, and more drastic problem with this approach is circular dependencies.

Consider the following:

public class A {
    private B b;

    public A() {
        this.b = new B();
    }
}

public class B {
    private C c;

    public B() {
        this.c = new C();
    }
}

public class C {
    private A a;

    public C() {
        this.a = new A();
    }
}

In the above example instantiating any of the 3 classes show will result in a program crash due to a stack overflow (or out of memory error), but there is arguably nothing “wrong” with the dependencies in each class. It’s not “wrong” for “A” to depend on “B” for example. This is because intra-constructor dependency allocation actually assumes a hierarchy where in fact there is none.

Because the author of each class is not necessarily aware of the effect of allocating the objects upon which they depend they are not aware that a circular dependency, possibly several steps removed will cause their program to crash catastrophically.

The one mitigation to the above approach is the use of singletons which is discussed more below.

Singleton until told otherwise

Despite what may seem like conventional thinking to the contrary, most of the time when a programmer wants to utilize the behaviour of a particular class they are not storing state. That is, in the process of storing or retrieving state many objects are used which themselves do not maintain state. This may include things like classes which perform operations on external systems like databases or email servers, utility functions and any algorithms or “business rules” that operate on data. These will typically perform operations on stateful objects, but themselves are not stateful.

These stateless objects therefore do not need more than one instance of themselves for a given application as all instances would be exactly the same. For this reason they are good candidates for a singleton. In fact it turns out that in more cases than not objects are used in a “singleton way” even though they may be allocated each time to do so. This is reflected in DI frameworks like Spring which defaults objects defined in the container to have singleton scope (that is, a single instance for the entire application).

The problems identified with the intra-constructor allocation approach discussed above are somewhat mitigated if all dependent objects allocated in constructors are simply references to singletons, however this hand-coded approach can result in a lot of additional boilerplate code being created.

Consider our A,B example as singletons:

public class A {

    private static final A instance = new A();

    private B b;

    private final A() {
        this.b = B.getInstance();
    }

    public static A getInstance() {
        return instance;
    }
}

public class B {

    private static final B instance = new B();

    private final B() {}

    public static B getInstance() {
        return instance;
    }
}

The mechanics to turn these objects into singletons is not difficult, but it is verbose and creates a lot of additional code.

The third (and arguably most preferred) option is to use a dedicated Dependency Injection framework. As previously stated these are only required for languages in which this form of runtime component selection and/or type definition is difficult. In more loosely typed languages such as JavaScript or Python the need for a dedicated DI framework is less because the dependency types can be overridden at runtime however there is an argument to suggest that a dependency injection framework may still provide benefit from a performance perspective and from the ability to define dependencies declaratively.

There are many dependency injection systems available both in Programmatic (dependencies defined in code) and Declarative (dependencies typically defined in a separate configuration) variants. Below is an example of a Declarative DI system using XML as the declaration language:

<beans>
    <bean id="writer" class="com.mypackage.DatabaseWriter"/>
    <bean id="emailer" class="com.mypackage.Emailer"/>
    <bean id="serializer" class="com.mypackage.Serializer"/>

    <bean id="doer" class="com.mypackage.DoesSomething">
        <property ref="writer"/>
        <property ref="emailer"/>
        <property ref="serializer"/>
    </bean>
</beans>

In this example our “DoesSomething” class has all its dependencies configured in an external config file which is used to bootstrap the Dependency Injection framework.

This might look something like this:

File config = ... // Get the XML file above
DependencyInjectionSystem.getInstance().init(config);
DoesSomething doer = DependencyInjectionSystem.getInstance().getBean("doer");
doer.doSomething("test");

In this case the DI system would have automatically set all the dependencies on the instance of the DoesSomething class it gave us and all we have to do is call our method.

Of course there are many variations of this and more mature frameworks such as Spring will allow the developer to simply annotate their class to declare its dependencies.

For example:

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {}

In this example because both A and B are declared with the class level @Component annotation the DI container knows how to allocate and assign these dependencies.

The result of this means the following will “just work”:

@Component
public class A {
    @Autowired
    private B b;

    public void doSomething() {
        // b is NOT null here because it was injected by the framework
        b.helloWorld();
    }
}

@Component
public class B {
	public void helloWorld() {
		// Hello world!
	}
}

Dependency Injection is a design pattern, not a framework

Although using a DI framework can make life a lot easier, it is technically not required. Dependency Injection is a Design Pattern, not a framework. It is simply an approach to software development which advocates the explicit or declarative nomination of dependencies at run time, rather than implicit or automatically allocated dependencies bound at compile time. Of course it makes a lot of sense to use an existing, mature framework to manage the process of injecting dependencies, but it’s also important that the underlying mechanics of how, and reasons why, are well understood.