Dependency Injection as a Tool for Testing

Phil Bennett
philipobenito
Published in
4 min readDec 7, 2016

Dependency injection is a simple design pattern that provides several benefits, one of which is the ability to vastly improve your unit test suite. In this post I am going to be looking at how this is done and how it relates to certain testing methodologies.

Originally posted 15 August 2013.

Please be aware that all code samples are massively simplified for the purpose of this post and do not necessarily follow best coding practices.

Unit Testing

“Unit testing is a method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine if they are fit for use. Intuitively, one can view a unit as the smallest testable part of an application.”

In PHP I like to view a unit as a method of a class. This allows me to run one or multiple unit tests on each method that I write, and to write the code to have a single responsibility and to follow the UNIX philosophy of “Do one thing, and do it well.”.

There are other types of testing that should be employed alongside your unit tests, but for the purpose of this post I will only be looking at unit tests.

A Unit

Let’s take a look at some code.

It is impossible to unit test this code without allowing the doSomething() method to instantiate the real object Bar . As soon as we instantiate the concrete implementation of a class, the test is no longer only testing this unit of code, and therefore we have increased the amount of places that the test can fail.

If we follow the definition of unit testing, this unit is not the “smallest testable part” of our application.

We can refactor the method to receive its dependency Bar as an argument and therefore inject it at runtime instead of instantiating it.

Dependency injection as a design pattern is that simple, now we can inject Bar as an argument of our method. In testing terms, this means that we can now verify state on Bar after the method is invoked.

This is still quite complicated and requires us to access a database or at the very least an abstraction of the database.

Martin Fowler describes the unit we are testing as the System Under Test and any dependencies that we interact with as Collaborators. By abstracting any collaborators, we are able to simplify the process and minimise the places where our test can fail. I want to know that if a test fails, it fails in the System Under Test.

Test Doubles

Martin Fowler describes the unit we are testing as the System Under Test and any dependencies that we interact with as Collaborators. By abstracting any collaborators, we are able to simplify the process and minimise the places where our test can fail. I want to know that if a test fails, it fails in the System Under Test.

  • Fake: A fake is a simpler implementation of a real object. For example, it may access an array of data rather than touching a database
  • Stub: A stub is used for providing the tested code with “indirect input” without any implementation. For example, the tested code may rely on invoking a method of another object that will return required data, this is “indirect input”.
  • Mock: A mock is used to verify “indirect output”. For example, the tested code may be required pass arguments to a method of another object, this is “indirect output”.

Testing with a Fake

By refactoring the test, we can inject a Fake of Bar and control the implementation of its methods.

By injecting a Fake we have isolated the testing to the system under test and we simply verify state on the collaborator to ensure it has changed as expected. Separate unit tests of the real Bar object will handle the testing of the real implementation.

Testing with a Stub

There are several methods and tools to aid the creation of stubs. One method would be to hard code the stub in a similar way to a fake, the difference here is that the stub provides no implementation but only returns a predefined value. For this example though, I will be using PHPUnit’s mocking framework to create the stub for me. Stay with me here, although it is a mocking framework, it is still possible to create a stub, I will explain the differences in creation later in the post.

By using a stub, we have provided a hard coded return value from our collaborator. We have also eliminated the need to verify state on our collaborator. This is a pure unit test. We provide the “indirect input” to be returned by the collaborator and only test the code in the system under test. Again, we are relying on the real implementation of Bar to have its own unit tests.

Testing with a Mock

As discussed earlier in this post, a mock is used for verification. We want the mock to ensure that the method is invoked the correct amount of times and that it has been passed the correct arguments or “indirect output”.

The difference here as opposed to the stub is that we are telling the object to verify that the method is invoked only once, and that the method is passed specific arguments. The mock will verify these conditions are met and throw an exception if they are not. This is essentially verifying state on the collaborator with “indirect output”.

And I’m Off

Hopefully this post has given an insight into just one of the reasons that dependency injection is such a powerful design pattern. It should not however, be mistaken for, or considered to be more than it is. Dependency injection is part of a larger concept and set of methods. I would recommend reading The “D” Doesn’t Stand For Dependency Injection by Brandon Savage for a little more clarification on this.

--

--

Phil Bennett
philipobenito

Software developer, creator of league/container and league/route.