Mocking ES and CommonJS modules with jest.mock()

Dominic Fraser
CodeClan
Published in
8 min readAug 31, 2018
Photo by NESA by Makers on Unsplash

Previously we introduced the basics of using Jest as a test runner, assertion library, and mocking library. If you have not used Jest before I recommend reading through the linked post first.

In order for a unit test to be worthwhile it must be reliable; it should not be reliant on hopeful consistency of external dependencies in order to pass. External dependencies can include any modules/packages external to the test subject, requests to a database, or requests to an API. If running the same test several times without change can result in different outcomes, the test loses value. If trust in the test is lost, less focus is inevitably paid to test failures.

Unit tests aim to test small units of code in isolation, testing how each behaves with specific input. The responsibility of testing how different parts of a system work together lies with integration tests.

Using Mocks gives us control over external dependencies - for the duration of the test we can replace code we do not have control over with code we do. You can mock both externally required/imported code, or code passed in through dependency injection. Mocking code passed in through dependency injection is the simpler of the two, and one argument towards using that approach. However, if your code is instead importing modules directly then Jest’s jest.mock() can be used to remove the external dependency when unit testing.

Here we will look at using Jest’s inbuilt mocking capabilities, covering:

  • Functions
  • Classes
  • CommonJS Modules vs ES Modules
  • Quirks of hoisting when using mock functions inside of manual mocks

Setup

First we need to install Jest using NPM:

npm install --save-dev jest

Then add a command to our package.json scripts:

"scripts": {
"test": "jest --watchAll"
}

Running npm test will now run Jest in watch mode.

Specifically for working with ES6 Modules in the third example we will also make sure to install Babel to compile to code Jest understands.

npm install --save-dev babel-preset-env

In our root folder we then add a .babelrc containing:

{
"presets": ["env"]
}

Mocking a Service

Let’s say we have a service that returns an object containing key value pairs of string keys and translated values per language. We are using this service within another module, and we are concerned not with what the service returns, but with how our second module interacts with it.

Exceptionally simplified code for the service could be:

translationsService.js
const getTranslations = () => {
// makes network call to retrieve translations
// then parses result into an object of the below shape
const result = {
strings: {
'british-english': {
keyOne: 'string-one',
},
'malaysian': {
keyOne: 'string-satu'
},
},
};
return result;
};
module.exports = getTranslations;

By writing this without the network call we can see the path towards mocking this, we have already established the shape of the data returned.

A very contrived example of where this could be used is below. Here we have a different module that uses the translations service to get an object of all translations stored by key identifiers, and returns a string including a response based on the language and a boolean value.

testSubject.jsconst getTranslations = require('../helpers/translationsService');const moduleWeAreTesting = (language, bool = true) => {
const trans = getTranslations();
const getString = stringKey => {
return trans['strings'][language][stringKey];
};
const res = bool ? getString('agree') : getString('disagree'); return `They say: ${res}`
};
module.exports = moduleWeAreTesting;

Here we could test the result of different arguments being passed in, but would not want to be dependant on the externally defined getTranslations function. For example, if the translation for a certain stringKey was updated our test would fail, even though it is not the unit test’s responsibility to be testing this.

Instead, we could mock the service as below:

const testSubject = require('./testSubject')jest.mock('../helpers/translationsService', () => () => ({  
strings: {
polish: {
agree: 'tak',
disagree: 'nie',
},
malaysian: {
agree: 'ya',
disagree: 'tidak',
},
},
}));
describe('testSubject tests', () => {
it('returns the correct string using default boolean', () => {
const moduleUnderTest = testSubject('malaysian');
expect(moduleUnderTest).toEqual('They say: ya');
});
it('returns the correct string on false', () => {
const moduleUnderTest = testSubject('polish', false);
expect(moduleUnderTest).toEqual('They say: nie');
});
});

As you can see, now the logic within the testSubject is what is under test, rather than the live integration between it and the external service. This still allows us to flag improvements, such as what happens if no translations are returned at all…

Mocking a Class

A class mock is very similar. Say we had a class as below:

myClass.jsclass MyClass {
constructor(name) {
this.name = name;
}
methodOne() {
return 1;
}
methodTwo() {
return 2;
}
}
export default MyClass;

If it was used within a file called testSubject.js, the mock in the test file would look as so:

import testSubject from './testSubject';jest.mock('./myClass', () => () => ({
name: 'Jody',
methodOne: () => 10,
methodTwo: () => 25,
}));

As this is an ES Module it is mocked as a function that returns an object. The return values are now predictable, allowing the logic of wherever it is used to be tested in isolation.

CommonJS Modules vs ES Modules

Above we saw modules created using both module.exports / require and export / import. These are two different module systems, and have an added complexity when used alongside each other. The first part of this is using Babel to compile CommonJS modules, as described in Setup.

To review how these are used let’s quickly look at these pairs, where our test subject is using code from an external module. We’ll use default exports, but the same principle applies to named exports.

The standard ES Module export / import syntax would be:

externalModule.jsconst ourCode = () => 'result';export default ourCode;
testSubject.jsimport ourCode from './externalModule';// use ourCode()

The standard CommonJS module.exports / require syntax would be:

externalModule.jsconst ourCode = () => 'result';module.exports = ourCode;
testSubject.js
const ourCode = require('./externalModule');// use ourCode()

Using module.exports and then import is also not a problem:

externalModule.jsconst ourCode = () => 'result';module.exports = ourCode;
testSubject.js
import ourCode from './externalModule';// use ourCode()

However, when using export with require we would see an error such as:

TypeError: ourCode is not a function

The CommonJS module does not understand the ES Module it is trying to require. This is easily fixed, by adding a .functionNameBeingExported to the require, which for a default export is default.

externalModule.jsconst ourCode = () => 'result';export default ourCode;
testSubject.js
const ourCode = require('./externalModule').default;// use ourCode()

Taking A as being no modification needed to the standard syntax, and B and some modification needed, we can see this illustrated below:

Module keywords combinations

This also directly relates to how Jest mocks will differ.

For type A these can be seen as a function returning a result:

jest.mock('.\externalModule', () => () => 'mock result');

For type B these can be seen as an object containing a function that returns a result:

jest.mock('.\externalModule', () => ({
default: () => 'mock result',
}));

Quirks of mock functions inside mocks

In the previous post we saw how we could inject Jest’s mock functions (created using jest.fn()) into React props, or directly into any function being tested.

This might look like:

const clickFn = jest.fn();const component = shallow(<MyComponent onClick={clickFn} />);// orfunction(arg1, arg2, clickFn);

This is exceptionally useful, with the return value able to be either directly specified when the mock function is declared, or dynamically using API methods such as .mockReturnValueOnce(value).

In the previous post an example was given where a mock module contained mock functions within it. We could amend the example given in Mocking a class to do the same:

import testSubject from './testSubject';const mockFunction = jest.fn();jest.mock('./myClass', () => () => ({
name: 'Jody',
methodOne: mockFunction,
}));

This would give us control over what mockFunction would return on a per test basis.

However, when testing examples for this post I found a limitation. This only works when testing subjects that themselves return a class. If the test subject is a standard function then an error of mockFunction is not defined will be returned.

This is due to hoisting. Jest docs state that:

Jest will automatically hoist jest.mock calls to the top of the module (before any imports). Since calls to jest.mock() are hoisted to the top of the file, it's not possible to first define a variable and then use it. An exception is made for variables that start with the word 'mock'. It’s up to you to guarantee that they will be initialized on time!

So what is the actual effect of this? If testing a file that returns a function then the order of execution from within the test file would be:

  • jest.mock()s
  • imports
  • inside imported test subject use the Jest mock
  • inside test file declare variable mockFunction
  • call test and invoke test subject

As you can see at the time the mock is used within the test subject the mockFunction has not been declared, it is undefined.

However, if the test subject was a class (regular or React), this becomes:

  • jest.mock()s
  • imports
  • inside imported test subject use the Jest mock
  • inside test file declare variable mockFunction
  • instantiate test subject inside test
  • call test using instantiated test subject

The key difference here is the instantiation. As this happens after mockFunction is declared then the class instance has access to its value.

This tripped me up for some time, knowing I had written code that used a Jest function within a Jest mock, but getting an error each time I attempted it using the example code in this post! This code was never going to work in that form. I have not yet seen a way to mock a function within a function, or class, without the outer being instantiated before the test runs.

However, if the file to be mocked is exporting several independent functions then they can be imported and mocked individually, thanks to Jest’s automock feature.

import testSubject from './testSubject';
import myHelper from ./myHelperFunctions;
jest.mock('./myHelperFunctions');describe('Tests', () => { beforeEach(() => {
myHelper.mockReset();
});
it('relies on the automock', () => {
functionThatCallsMyHelper();
expect(myHelper).toHaveBeenCalled();
});
it('is testing a specific implementation of the function', () => {
myHelper.mockReturnValueOnce('some value');
expect(functionThatCallsMyHelper()).toBe(false);
});
});

As the function to be mocked is imported it changes the order of execution and avoids the above error.

Final thoughts

Hopefully the examples and thoughts given here provide some help, or inspiration, around mocking with Jest. It is definitely worth exploring the mock functions and expectations API documentation.

Thanks for reading 😁 If you liked this, you may like other things I have written including:

Resources:

--

--