Mastering Unit Testing: How to Mock Dependencies with Jest

Fang Jin
Bits and Pieces
Published in
5 min readMar 25, 2023

--

Photo by Sigmund on Unsplash

I started to practice test-driven programming a couple of years back. Depending on each project's settings, the strictness of the unit test can vary. For instance, one project requires to have a coverage coupled so if the percentage isn’t met, the code can’t be committed. Therefore I have to refresh all the possible ways to mock things under Jest.

Baseline of the mocking

Let’s start with easy ones with a function that is relatively easy to test:

function app = (fn) => {
const a = fn()
...
return ...
}

In order to test app we can make a mock replacement for fn and then pass it into your app as usual:

test('app', () => {
let mockedFn = jest.fn()
app(mockedFn)

expect(mockedFn).toHaveBeenCalled()
})

Not only the above approach allows our function to be tested, but also it provides us an extreme level of confidence. As long as we can provide a fn we can control the robustness of this unit, even when it’s a black box with no access to the implementation. Notice the reason for our confidence level isn’t based on luck, instead, it’s that the dependencies are all declared on the interface. Exactly for this reason, when designing your own function, you should aim for this goal because it’ll make your life a lot easier.

When testing gets difficult

Unfortunately, we can’t always ask for the perfect interface design. Half of the time, unless writing a library from scratch, you will run into a case where the written function doesn’t carry that implicit interface. Let’s take a look at one example:

import axios from 'axios'

function app() {
const user = axios.get('qplot.com');
if (!user) return null

// now we can use this user
return ...
}

We want to test a function where inside we fetch the user over the internet and then we move to the rest of the code based on this user object. Naive or small as the function looks, it’s kind of very difficult to write a test. Believe it or not, this is the number one place where a junior developer quits the test job that he is supposed to perform. Let’s take a second to understand why it’s that.

The function app doesn’t have input parameters, aka explicit interface, however, it has a hidden dependency axios which isn’t written but referenced inside. Reflecting on what we have stated at the beginning unless we can control all dependencies precisely, we won’t gain the confidence that we need. Therefore, the solution is to find a way to mock axios so that we can control how it behaves in the testing environment. Let me show you one quick example of mocking it:

import axois from 'axois' 

jest.mock('axios', () => ({
get: (url) => Promise.resolve('fang')
}))

test('app', () => {
app()
// do rest of testing
})

We first import the axios and acknowledge there’s a get function that we want to mock the response. Therefore whenever inside the app when axios.get is called, it’ll run our mocked version, in this particular test we want to set user equals to fang without going a round trip to the internet.

Ok, let’s retrospect a bit, in order to test a unit, we should find out all apparent and hidden dependencies, and make sure you can mock all of them to recover the perfect crime scene. After that based on these capabilities, then you can come up with any number of tests to back your functionalities.

People might argue here, mocking axios seems like dodging the testing responsibility. This is where the unit testing and rest of the testings start to branch out. Here we don’t really care about axios , in a way, even when axios is broken, we’d like to finish our test. This is actually the definition of a unit test in a verbose form.

You might wonder why we ever want to have hidden dependencies. This is a more philosophical question: if you live in a world, you can’t live by yourself, no matter how hard you try, you will end up with some relationship that is not apparent to the surface. Writing a function with all sorts of dependencies operates in a similar fashion. Technically, you can call the source of these dependencies from global variables, external libraries, or side effects. However you understand them, they are in general not under our control.

💡 Dependency-management becomes infinitely more streamlined if you use Bit. Within a Bit Workspace, you don’t need to tell npm, pnpm, or yarn which component dependencies need to be installed, nor if it’s a dev or prod dependency. Bit dynamically generates package.json files for each, and can handle this for you painlessly. Find out more about this here and here.

Learn more here:

Summary

I showed you ways to mock both apparent and hidden dependencies. So when you write tests, find them first, and then mock them, happy coding ;)

Build Apps with reusable components, just like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo

--

--

#OpenToWork Front-end Engineer, book author of “Designing React Hooks the Right Way” sold at Amazon.