Dependency Injection: Practical Examples for Testing and Refactoring in JavaScript

Daniel King
7 min readDec 1, 2016

--

Three Rules of Work: Out of clutter find simplicity; From discord find harmony; In the middle of difficulty lies opportunity ~ Albert Einstein

As someone who builds software, I find myself thinking a lot about how to organize my code. Good quality code organization is one of the main factors that makes a large software system flexible, easy to change, easy to reason about, and easy to test. On the flip side, if code is poorly organized, the system can become a spaghettified mess that is all but impossible to reason about or change, and if you have worked on a codebase of any significant size, you have probably struggled with the question of how to test some aspect of that system.

In this article, I would like to discuss one of the popular patterns of code organization: Dependency Injection. In the process, we will look at a practical example that will allow us to see ways in which this pattern can help us refactor and test our code.

But first…

What is Dependency Injection?

According to Wikipedia,

In software engineering, dependency injection is a software design pattern that implements inversion of control for resolving dependencies. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it.

Blah blah blah…but what does it all mean? Here’s my favorite mental model:

If a function/object/module depends on some other function/object/module, it should not go out and get what it needs. Instead, we explicitly give it anything it needs, typically by passing it in as a function parameter.

To see what this means in action, let’s look at an example.

An Example to Get Us Started

If you would like to see and follow along with the code for this article, you can find the finished example on my GitHub repository.

In order to get the most out of this article, you should have at least some familiarity with:

  1. Promises
  2. The XMLHttpRequest API
  3. Unit testing with Mocha and Chai
  4. Closures in JavaScript

Suppose that we are writing a client-side module, and we want to write a Promise-based function that makes an http GET request to a provided URL. If you have experience with JavaScript, then you probably know that there are plenty of existing libraries that will do this for you, but this example will serve to illustrate the concept. The usage of the function would look something like this:

To implement this function, we might initially do something like this:

However, there is a lot going on in this function. Let’s try to refactor it to make it a little easier to reason about.

One thing that would be natural to factor out of this function is the event handler that fires when the http response comes back. We might try doing something like what we have below, but there is a huge problem lurking in this example. See if you can spot it before moving on:

You may have noticed that the onHttpResponse function depends on the httpRequest, resolve and reject variables, but those variables do not exist at the location where the event handler function is now defined. So, how can we overcome this issue? By using dependency injection!

Dependency Injection as a Strategy for Refactoring

Since the onHttpResponse function depends on the httpRequest, resolve and reject variables, we can explicitly pass those dependencies into the function as parameters, as shown below. This is a basic example of dependency injection.

This example is almost correct, but there is still a very sneaky error hiding here. Once again, see if you can spot it before moving on:

The problem here is that the onHttpResponse function is an event handler function. This means that we have no control over what gets passed into the function as arguments when it is invoked. An event handler is always invoked with only one argument, namely the event object. We have a dilemma: We want to be able to pass httpRequest, resolve and reject into the onHttpResponse function as parameters, but when it is invoked, it will only be invoked with the event object.

To resolve this dilemma, we can turn the onHttpResponse function into a higher order function that takes in the dependencies as parameters and returns the desired event handler function:

Notice how httpRequest, resolve and reject are being passed into the onHttpResponse function on line 4, which returns the event handler function, which is assigned to the onload property of the http request. Even though the event handler function is not explicitly receiving resolve, reject, and httpRequest as parameters, it still has access to these variables because they existed at the moment it was defined (read more about closure if this point is confusing).

As a side note, this kind of higher order function can be written very succinctly using ES6 arrow function syntax:

What have we done so far? We used dependency injection as a tool to take a large, complicated function, and refactor it into smaller, more manageable chunks. However, it turns out that we also get something else for free: Those functions can now be easily tested!

Dependency Injection as a Strategy for Testing

Now that the onHttpResponse function is explicitly accepting httpRequest, resolve and reject as parameters, we can now use a library like sinon.js to test this function by passing in fake versions of those parameters called spies. This strategy of passing in fake versions of dependencies for testing purposes is often referred to as mocking.

When writing a test, it is important to think carefully about what the behavior of the function should actually be. The event handler created by onHttpResponse should do the following:

  1. If the response comes back with a status code of 200, it should invoke resolve and pass in the response body.
  2. If the response comes back with some other status code (say, 404), it should invoke reject and pass in an error object.

Here is what a test might look like:

A couple of things to notice in this example:

  1. Before each test case, resolve and reject are replaced with spies, which are functions that can report back whether or not they were invoked. They can also tell you what arguments they were invoked with.
  2. In each test case, an object literal is passed into the httpRequest parameter of the onHttpResponse function, which takes the place of the real http request that would ordinarily be passed in. This object literal has the status code and the response text hard-coded.
  3. Although the onHttpResponse function is intended to work as part of a system that makes http requests, this test is not actually making any external requests. This means that we are testing this function in isolation, eliminating the possibility that our tests could give unpredictable results due to outside factors.

Now that we have the onHttpResponse function working and tested, let’s look at the get function, which was really the whole point of this example. Here’s where we were:

In its current form, the get function would be fairly difficult to test, because it is making an http request to some external server. One way to make this more testable is to use — you guessed it — dependency injection! Namely, instead of allowing the function to create its own XMLHttpRequest object, we can pass one in as a parameter to the function:

Now, if we want to test this function, we can replace the http request with a fake containing spies, similar to what we did with resolve and reject before. This will allow us to test the function without needing to rely on any external server. Our testing file will now look like this:

This is a good thing, but we now have a problem that can crop up frequently when using dependency injection: The function’s interface can get cumbersome to use. Now, whenever we want to use the get function, we have to explicitly pass in a (real) http request object:

This is not so bad in this case, but when the system gets more complicated this can result in some painfully complicated interfaces. So, let’s finish off this example by cleaning up the interface.

Cleaning Up the Interface

There are many ways that we could simplify the interface in this example. One way or another, our goal will be to have the get function no longer require explicitly passing in an http request object. So the usage of the function should be like it was before:

But, how can we accomplish this without sacrificing our ability to test the function? One way is to put a default value on the httpRequest parameter, which we can choose to override if we want to pass in our own fake http request:

Now, when we run our tests, we can explicitly pass in a fake http request, but when we use the get function in other parts of the system, it will create its own real http request object by default.

Final Thoughts

Dependency injection is not a magic bullet that will solve all of your problems for you. As we discussed, it can sometimes lead to function interfaces becoming very complicated and hard to use. However, when used judiciously, it can become a powerful tool that allows us to write more flexible, testable code.

It’s also worth noting that the examples of dependency injection that I showed here are extremely simple, compared to some more “real-world” usages of the technique. However, I hope that this has been enough to get you comfortable with the concept and has made a practical case for why it is an effective design principle. Check out part 2 for a look at more realistic examples!

Happy coding!

If you enjoyed this article or found it helpful, please click the heart below to recommend it to others who might like it as well!

You might also enjoy learning about:

Daniel King is a professional software engineer and educator in the Los Angeles area who is available for software development and coaching on a contract basis.

daniel.oliver.king@gmail.com

--

--

Daniel King

Professional Software Engineer and Educator, amateur Musician, armchair Personal Finance Expert