Mocking ES and CommonJS modules with jest.mock()
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.jsconst ourCode = require('./externalModule');// use ourCode()
Using module.exports
and then import
is also not a problem:
externalModule.jsconst ourCode = () => 'result';module.exports = ourCode;
testSubject.jsimport 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.jsconst 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:
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 tojest.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:
- Want to get into tech? It’s never too early to start attending meetups.
- Mocking HTTP requests with Nock