Dependency Injection part 2: A (More) Practical Example for Testing in JavaScript

Daniel King
5 min readDec 23, 2016

--

Business and politics have a wholesome and an unwholesome interface. You have to eliminate the unwholesome interface. ~ Salman Khurshid

In the last article, I discussed the technique of dependency injection as a practical strategy for making our code easier to test and refactor. This time around, I’d like to dig in a little farther and show a larger scale example in order to demonstrate how dependency injection is really used in practice.

But first, to provide some context, let’s talk about a very far-reaching and important topic in software development: Interfaces.

Interfaces

Last time, I described dependency injection as follows:

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.

This concept touches on the very deep concept of interfaces, which (broadly) refers to the ways in which different parts of a system interact with each other.

We encounter interfaces all over our daily lives without even thinking about it. Think about a smartphone: To interact with the phone, you don’t open it up and fiddle around with the electronics inside (or, at least, most people don’t) — instead, you just use the few buttons and touchscreen that are provided on the outside of the phone. These few buttons and touchscreen form the interface of the phone, and because they are there, you don’t need to know the internal details of how the phone works in order to use it — those details are encapsulated, because the phone presents a clean, wholesome interface to the outside world.

The same principle holds true in software: To the extent possible, we want to create functions and modules that are self-contained and present a clean, wholesome interface to the outside world, rather than having their internal mechanisms tangled up with things outside. The essential purpose of dependency injection is to allow us to create clean interfaces where the different parts of our system can interact, rather than jumbling them together.

In the last article, I mainly looked at dependency injection at the function level. This time, we’re going to scale it up a bit and look at dependency injection at the module level, which is the most common use case that people think of when they hear the term “dependency injection”.

Disclaimer: This article was heavily inspired by this excellent FunFunFunction video, which was in turn inspired by this excellent talk on boundaries. Check out those resources if you’re interested in learning more!

Without further ado…

Dependency Injection at the Module Level

If you want to follow along on an actual codebase, you can find the complete code for this example on github.

Suppose that you were creating some kind of blog website, and you have a database full of articles. One day, your boss asks you to create a piece of functionality that gets the titles of the most popular articles on the blog. Your initial inclination might be to create something like this:

Here, we are creating an article controller which has a getPopularTitles method. This method queries the database for the titles of all of the articles that have over a certain number of “likes”. Note that we are using the knex library, which is a popular way of interacting with SQL databases in JavaScript.

However, notice that this module is not self-contained. It imports knex itself and then directly interacts with the database, which means that if we try to test this module, we will end up querying the real database. This module is not presenting a clean, wholesome interface to the outside world. Let’s fix this by using dependency injection. Instead of importing knex and exporting the article controller object directly, this module will export a function which takes in the dependencies as an object parameter and returns the article controller object:

Now, notice that the article controller does not import anything itself. Therefore, it is self-contained, and it is presenting a clean interface to the outside world. This will make it much easier to test in isolation.

Testing the Module

Now that we have cleaned up the module using dependency injection, we can test it by passing in a mock knex object, instead of the real knex library. Let’s think about what this mock needs to look like.

Referring back to the article controller code above, we can see that the knex object is directly called. This means that our mock knex object needs to be a function. However, after the function is called, a select method and a where method are chained directly afterward. Here’s how we can mock that: our knex mock will be a function, but since function are just objects in JavaScript, we will also attach select and where methods to this function. When the function itself or either of its methods are called, they will simply return the original function again, allowing them to be chained indefinitely.

In order to confirm that the correct query is performed on the database, we will use spies from the sinon.js library.

With all that in mind, here’s what our test file might look like. Make sure to read through it carefully and notice what’s going on:

Notice that we call the createKnexMock function to make our mock database connection, complete with sinon spies. Then, we call createArticleController and pass in the mock knex object. This allows us to test the article controller object in isolation, without needing to connect to an actual database.

Final Thoughts

If you found this article helpful in terms of understanding how dependency injection can help us make our modules more self-contained and our interfaces more clean, I definitely recommend checking out the FunFunFunction video on mocking and this excellent talk on boundaries, which will go into more detail about similar ideas. 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

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