Using Mocks for Testing in JavaScript With Jest

Introduction

Jest is a popular, open-source test framework for JavaScript. We can use Jest to create mocks in our test - objects that replace real objects in our code while it's being tested.

In our previous series on unit testing techniques using Sinon.js, we covered how we can use Sinon.js to stub, spy, and mock Node.js applications - particularly HTTP calls.

In this series, we are going to cover unit testing techniques in Node.js using Jest. Jest was created by Facebook and integrates well with many JavaScript libraries and frameworks like React, Angular and Vue to name a few. It has a particular focus on simplicity and performance.

In this article, we will review what mocks are, then focus on how we can set up Jest for a Node.js application to mock an HTTP call in our test. We will then compare how we use Jest and Sinon to create mocks for our programs.

What are Mocks?

In unit testing, mocks provide us with the capability to stub the functionality provided by a dependency and a means to observe how our code interacts with the dependency. Mocks are especially useful when it's expensive or impractical to include a dependency directly into our tests, for example, in cases where your code is making HTTP calls to an API or interacting with the database layer.

It is preferable to stub out the responses for these dependencies, while making sure that they are called as required. This is where mocks come in handy.

Let us now see how we can use Jest to create mocks in Node.js.

Setting up Jest in a Node.js Application

In this tutorial, we will set up a Node.js app that will make HTTP calls to a JSON API containing photos in an album. Jest will be used to mock the API calls in our tests.

First, let's create the directory under which our files will reside and move into it:

$ mkdir PhotoAlbumJest && cd PhotoAlbumJest

Then, let's initialize the Node project with the default settings:

$ npm init -y

Once the project is initialized, we will then proceed to create an index.js file at the root of the directory:

$ touch index.js

To help us out with the HTTP requests, we'll be using Axios.

Setting up Axios

We will be using axios as our HTTP client. Axios is a lightweight, promise-based HTTP client for Node.js which can also be used by web browsers. This makes it a good fit for our use case.

Let's first install it:

$ npm i axios --save

Before using axios, we'll create a file called axiosConfig.js through which we'll configure the Axios client. Configuring the client allows us to use common settings across a set of HTTP requests.

For example, we can set authorization headers for a set of HTTP requests, or most commonly, a base URL which will be used for all HTTP requests.

Let's create the configuration file:

touch axiosConfig.js

Now, let's access axios and configure the base URL:

const axios = require('axios');

const axiosInstance = axios.default.create({
    baseURL: 'https://jsonplaceholder.typicode.com/albums'
});

module.exports = axiosInstance;

After setting the baseURL, we've exported the axios instance so that we can use it across our application. We'll be using www.jsonplaceholder.typicode.com which is a fake online REST API for testing and prototyping.

In the index.js file we created earlier, let's define a function that returns a list of photo URLs given an album's ID:

const axios = require('./axiosConfig');

const getPhotosByAlbumId = async (id) => {
    const result = await axios.request({
        method: 'get',
        url: `/${id}/photos?_limit=3`
    });
    const { data } = result;
    return data;
};

module.exports = getPhotosByAlbumId;

To hit our API we simply use the axios.request() method of our axios instance. We pass in the name of the method, which in our case is a get and the url which we will invoke.

The string we pass to the url field will be concatenated to the baseURL from axiosConfig.js.

Now, let's set up a Jest test for this function.

Setting up Jest

To set up Jest, we must first install Jest as a development dependency using npm:

$ npm i jest -D

The -D flag is a shortcut for --save-dev, which tells NPM to save it as a development dependency.

We will then proceed to create a configuration file for Jest called jest.config.js:

touch jest.config.js

Now, in the jest.config.js file, we'll set the directories in which our tests will reside:

module.exports = {
    testMatch: [
        '<rootDir>/**/__tests__/**/?(*.)(spec|test).js',
        '<rootDir>/**/?(*.)(spec|test).js'
    ],
    testEnvironment: 'node',
};

The testMatch value is an array of global patterns that Jest will use to detect the test files. In our case, we are specifying that any file inside the __tests__ directory or anywhere in our project that has either a .spec.js or .test.js extension should be treated as a test file.

Note: In JavaScript, it's common to see test files end with .spec.js. Developers use "spec" as shorthand for "specification". The implication is that the tests contain the functional requirements or specification for the functions being implemented.

The testEnvironment value represents the environment in which Jest is running, that is, whether in Node.js or in the browser. You can read more about other allowable configuration options here.

Now let's modify our package.json test script so that it uses Jest as our test runner:

"scripts": {
  "test": "jest"
},

Our setup is done. To test that our configuration works, create a test file at the root of the directory called index.spec.js:

touch index.spec.js

Now, within the file, let's write a test:

describe('sum of 2 numbers', () => {
    it(' 2 + 2 equal 4', () => {
        expect(2 + 2).toEqual(4)
    });
});

Run this code with the following command:

$ npm test

You should get this result:

 PASS  ./index.spec.js
  sum of 2 numbers
    ✓ 2 + 2 equal 4 (3ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.897s, estimated 1s
Ran all test suites.

With Jest set up correctly, we can now proceed to write the code to mock our HTTP call.

Mocking an HTTP Call With Jest

In the index.spec.js file, we'll start fresh, deleting the old code and writing a new script that'll mock an HTTP call:

const axios = require('./axiosConfig');
const getPhotosByAlbumId = require('./index');

jest.mock('./axiosConfig', () => {
    return {
        baseURL: 'https://jsonplaceholder.typicode.com/albums',
        request: jest.fn().mockResolvedValue({
            data: [
                {
                    albumId: 3,
                    id: 101,
                    title: 'incidunt alias vel enim',
                    url: 'https://via.placeholder.com/600/e743b',
                    thumbnailUrl: 'https://via.placeholder.com/150/e743b'
                },
                {
                    albumId: 3,
                    id: 102,
                    title: 'eaque iste corporis tempora vero distinctio consequuntur nisi nesciunt',
                    url: 'https://via.placeholder.com/600/a393af',
                    thumbnailUrl: 'https://via.placeholder.com/150/a393af'
                },
                {
                    albumId: 3,
                    id: 103,
                    title: 'et eius nisi in ut reprehenderit labore eum',
                    url: 'https://via.placeholder.com/600/35cedf',
                    thumbnailUrl: 'https://via.placeholder.com/150/35cedf'
                }
            ]
        }),
    }
});

Here, we first import our dependencies using the require syntax. Since we do not want to make any actual network calls, we create a manual mock of our axiosConfig using the jest.mock() method. The jest.mock() method takes the module path as an argument and an optional implementation of the module as a factory parameter.

For the factory parameter, we specify that our mock, axiosConfig, should return an object consisting of baseURL and request(). The baseUrl is set to the base URL of the API. The request() is a mock function that returns an array of photos.

The request() function we've defined here replaces the real axios.request() function. When we call the request() method, our mock method will be called instead.

What's important to note is the jest.fn() function. It returns a new mock function, and its implementation is defined within the parentheses. What we've done via the mockResolvedValue() function is provide a new implementation for the request() function.

Typically, this is done via the mockImplementation() function, though since we're really only just returning the data which holds our results - we can use the sugar function instead.

mockResolvedValue() is the same as mockImplementation(() => Promise.resolve(value)).

With a mock in place, let's go ahead and write a test:

describe('test getPhotosByAlbumId', () => {
    afterEach(() => jest.resetAllMocks());

    it('fetches photos by album id', async () => {
        const photos = await getPhotosByAlbumId(3);
        expect(axios.request).toHaveBeenCalled();
        expect(axios.request).toHaveBeenCalledWith({ method: 'get', url: '/3/photos?_limit=3' });
        expect(photos.length).toEqual(3);
        expect(photos[0].albumId).toEqual(3)
    });
});

After each test case, we ensure that the jest.resetAllMocks() function is called to reset the state of all mocks.

In our test case, we call the getPhotosByAlbumId() function with an ID of 3 as the argument. We then make our assertions.

The first assertion expects that the axios.request() method was called, while the second assertion checks that the method was called with the correct parameters. We also check that the length of the returned array is 3 and that the first object of the array has an albumId of 3.

Let's run our new tests with:

npm test

We should get the following result:

PASS  ./index.spec.js
  test getPhotosByAlbumId
    ✓ fetches photos by album id (7ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.853s, estimated 1s
Ran all test suites.

With this new familiarity and experience, let's do a quick comparison of the testing experiences with Jest and Sinon, which is commonly used for mocking as well.

Sinon Mocks vs Jest Mocks

Sinon.js and Jest have different ways they approach the concept of mocking. The following are some of the key differences to note:

  • In Jest, Node.js modules are automatically mocked in your tests when you place the mock files in a __mocks__ folder that's next to the node_modules folder. For example, if you a file called __mock__/fs.js, then every time the fs module is called in your test, Jest will automatically use the mocks. On the other hand, with Sinon.js you must mock each dependency manually using the sinon.mock() method on each test that needs it.
  • In Jest, we use the jest.fn().mockImplementation() method to replace a mocked function's implementation with a stubbed response. A good example of this can be found in the Jest documentation here. In Sinon.js, we use the mock.expects() method to handle that.
  • Jest provides a large number of methods for working with their mock API and particularly with modules. You can view all of them here. Sinon.js, on the other hand, has fewer methods to work with mocks and exposes a generally simpler API.
  • Sinon.js mocks ship as part of the Sinon.js library which can be plugged in and used in combination with other testing frameworks like Mocha and assertion libraries like Chai. Jest mocks, on the other hand, ship as part of the Jest framework, which also ships with its own assertions API.

Sinon.js mocks are often most beneficial when you're testing a small application that may not require the entire power of a framework like Jest. It's also useful when you already have a test setup and need to add mocking to a few components in your application.

However, when working with large applications that have many dependencies, leveraging on the power of Jest's mock API alongside its framework can be greatly beneficial to ensure a consistent testing experience.

Conclusion

In this article, we have looked at how we can use Jest to mock an HTTP call made with axios. We first set up the application to use axios as our HTTP request library and then set up Jest to help us with unit testing. Finally, we've reviewed some differences between Sinon.js and Jest mocks and when we could best employ either.

To read more about Jest mocks and how you can leverage them for more advanced use cases, check out their documentation here.

As always, the code from this tutorial can be found on GitHub.

Author image
About Allan Mogusu
Nairobi, Kenya Twitter