Universally Testable Dependencies in JavaScript

Contents

JavaScript dependencies are notoriously hard to mock and test across framework
and environments. However, Dependency Injection (DI) ensures testability. It
is common in statically typed languages, like Java or Go, but is also useful in
dynamic languages like JavaScript.

This article will guide you through a simple technique that can apply
to any JavaScript code base, frontend or backend, independent of frameworks
and libraries.

The technique I am going to demonstrate is arguably not true DI, since the
dependencies are still managed by the module using them. However, it
solves the dependency problem for testing which is the focus of this
article.

Why Do You Need DI?

At the most basic level, you need DI to avoid testing your dependencies.
Not testing dependencies is a good idea for two reasons:

Your dependencies should already be tested. This applies to both external dependencies as well as from your own project. If an external dependency doesn’t have good testing, you probably shouldn’t be using it or you should help them and add some tests!

Dependencies can do too much. Since dependencies exist outside of
your module’s control, they can perform actions your module shouldn’t be concerned with such as saving data, making web requests, or being algorithmically heavy.

Your tests will be less complicated and faster if you avoid tesing dependencies because of the reasons mentioned above.

Setting Up Your Code to Enable DI

I am using Babel and
Jest throughout this guide. However,
you can easily adapt this technique to any JavaScript setup.

To start off, put all dependencies which should not execute during
a test into an exported deps object.

The beforeAll block is the most important piece of code. Because
we are using functions from the deps object in our module code, we can
just replace deps.longRunningFunction with anything we want before invoking
the module’s default function.

In this example, we are using Jest’s built in mockjest.fn().
It can produce a mock value to verify that our function returns the correct value and is called with the expected arguments.

The best part about testing this way is that we don’t have to worry about
any library’s spying functions staying up to date with the JavaScript
specifications. Often times, spies don’t work the same for all module syntaxes.
In fact, we don’t have to use the mocks at all! We could write our
own function with custom validators and set it as
deps.longRunningFunction.

Wrapping Up

This technique helped me immensely when maintaining long running or large
JavaScript projects. It provided a minimal code approach that is easy to
follow even when you are brand new to the code base. I hope you found this
approach useful and less complicated than other DI approaches.