How to mock a javascript module with jest
In this post we will see how to mock a javascript module using jest. This post assume that you already have knowledge about Javascript and testing.
How modules caching works with node.js ?
The node.js documentation says: ”Modules are cached after the first time they are loaded”. This means that every other call that require the same file will get exactly the same object.
Let’s see how it works with a concrete example. First we will create a file target.js
1module.exports = {2 example: () => console.log("I'm the original module"),3};
Inside we have nothing crazy. It’s just a simple module.exports
of an object that contain the key example
and the value is a function that calls console.log
. Now let’s create another file example.js
1const targetPath = require.resolve('./target.js');23console.log(require.cache[targetPath]);4const target = require('./target');5console.log(require.cache[targetPath]);67target.example();
Now we will execute our example file with node: node ./example.js
and we can see:
- The first log show
undefined
it means that the module is not loaded into the yet because it was not required for the moment - The second log show the cached module !1{2 id: 'FULL_PATH/target.js',3 exports: { example: [Function: example] },4 loaded: true,5 // Other elements ...6 }
- Then we see that we called the original module1I'm the original module
In this module we can see:
- id which is the path of the file
- exports which is the exported content of the file
- loaded which is the loaded state of the module
So when a module is loaded it goes to cache, why not just putting our mock in the cache to override it ?
Let’s try that open example.js
again. We will add the loaded value to true set name and set the id to the target path. Then the most important part is in export
, this is where we will add our mock.
1const targetPath = require.resolve('./target.js');2require.cache[targetPath] = {3 loaded: true,4 id: targetPath,5 exports: {6 example: () => console.log("I'm mocked"),7 },8};910const target = require('./target');11console.log(require.cache[targetPath]);1213target.example();
We can run that file again: node ./example.js
. We can see ""I'm mocked"
which mean the original module is not required anymore. Our mock is called !
How to restore to use the original module ? It’s actually pretty simple to invalidate the cache, you can do delete require.cache[targetPath]
and on the next require it will not use your mocked module.
So now you know how node modules caching works and how testing tools will mock them. It think this is important to know what’s happening under the hoods so you can use the tool more efficiently. Let’s now see how we can do that using jest.
How to mock an object with jest ?
To mock a javascript module using jest we can use the mock method. This method takes two arguments:
- the path of the module
- a factory that create the mock object
1const target = require('./target');2jest.mock('./target', () => ({3 example: jest.fn(() => console.log("I'm mocked")),4}));
Doing that jest will do what we’ve made in the previous section by overriding the module cache.
ES Modules mocking
But how this can works with ES modules imports as they must be on top of the file ? Actually calls to jest.mock
are hoisted on the top of the file. This is why we have that magic. The jest documentation talk about that in a dedicated section:
— If you’re using ES module imports then you’ll normally be inclined to put your import statements at the top of the test file. But often you need to instruct Jest to use a mock before modules use it. For this reason, Jest will automatically hoist jest.mock calls to the top of the module (before any imports).