Mock only one function from module but leave rest with original functionality

JavascriptUnit TestingJestjs

Javascript Problem Overview


I only want to mock a single function (named export) from a module but leave the rest of the module functions intact.

Using jest.mock('package-name') makes all exported functions mocks, which I don't want.

I tried spreading the named exports back into the mock object...

import * as utils from './utilities.js';

jest.mock(utils, () => ({
  ...utils
  speak: jest.fn(),
}));

but got this error:

> The module factory of jest.mock() is not allowed to reference any out-of-scope variables.

Javascript Solutions


Solution 1 - Javascript

The highlight of this answer is jest.requireActual(), this is a very useful utility that says to jest that "Hey keep every original functionalities intact and import them".

jest.mock('./utilities.js', () => ({
  ...jest.requireActual('./utilities.js'),
  speak: jest.fn(),
}));

Let's take another common scenario, you're using enzyme ShallowWrapper and it doesn't goes well with useContext() hook, so what're you gonna do? While i'm sure there are multiple ways, but this is the one I like:

import React from "react";

jest.mock("react", () => ({
  ...jest.requireActual("react"), // import and retain the original functionalities
  useContext: jest.fn().mockReturnValue({foo: 'bar'}) // overwrite useContext
}))

The perk of doing it this way is that you can still use import React, { useContext } from "react" in your original code without worrying about converting them into React.useContext() as you would if you're using jest.spyOn(React, 'useContext')

Solution 2 - Javascript

The most straightforward way is to use jest.spyOn and then .mockImplementation(). This will allow all other functions in the module to continue working how they're defined.

For packages:

import axios from 'axios';

jest.spyOn(axios, 'get');
axios.get.mockImplementation(() => { /* do thing */ });

For modules with named exports:

import * as utils from './utilities.js';

jest.spyOn(utils, 'speak');
utils.speak.mockImplementation(() => { /* do thing */ });

Docs here: https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname

Solution 3 - Javascript

For me this worked:

const utils = require('./utilities.js');
...
jest.spyOn(utils, 'speak').mockImplementation(() => jest.fn());

Solution 4 - Javascript

jest.requireActual inside of jest.mock seems like the way to go, however I needed to add a proxy instead of the object spread to prevent the type error Cannot read properties of undefined (reading ...) which can occur in certain import scenarios.

This is the final result:

jest.mock('the-module-to-mock', () => {
  const actualModule = jest.requireActual('the-module-to-mock')

  return new Proxy(actualModule, {
    get: (target, property) => {
      switch (property) {
        // add cases for exports you want to mock
        // 👇👇👇
        case 'foo': {
          return jest.fn() // add `mockImplementation` etc
        }
        case 'bar': {
          return jest.fn()
        }
        // fallback to the original module
        default: {
          return target[property]
        }
      }
    },
  })
})

Solution 5 - Javascript

I took Rico Kahler's answer and created this general purpose function:

function mockPartially(packageName: string, getMocks: (actualModule: any) => any) {
  jest.doMock(packageName, () => {
    const actualModule = jest.requireActual(packageName);
    const mocks = getMocks(actualModule);

    return new Proxy(actualModule, {
      get: (target, property) => {
        if (property in mocks) {
          return mocks[property];
        } else {
          return target[property];
        }
      },
    });
  });
}

and you use it like this for example to mock lodash:

mockPartially('lodash', (_actualLodash) => { //sometimes you need the actual module
   return {
      'isObject': () => true, //mock isObject
      'isArray': () => true // mock isArray
   }
});

Solution 6 - Javascript

Manual Mocks

You can create __mocks__ directory in the same level as utilities.js and then create a file with name utilities.js inside this directory.

utilities.js
const speak = () => "Function speak";
const add = (x, y) => x + y;
const sub = (x, y) => x - y;

module.exports = { speak, add, sub };

Now, keep everything as is and just mock the speak function.

__mocks__/utilities.js
const speak = jest.fn(() => "Mocked function speak");
const add = (x, y) => x + y;
const sub = (x, y) => x - y;

module.exports = { speak, add, sub };

And now you can mock utilities.js

utilities.test.js
const { speak, add, sub } = require("./utilities");

jest.mock("./utilities");

test("speak should be mocked", () => {
  expect(speak()).toBe("Mocked function speak");
});
Mocking Node Modules

Create a directory named __mocks__ in the same level as node_modules and add a file 'axios.js' inside this directory.

__mocks__/axios.js
const axios = {
  get: () => Promise.resolve({ data: { name: "Mocked name" } }),
};

module.exports = axios;
fetch.js
const axios = require("axios");

const fetch = async () => {
  const { data } = await axios.get(
    "https://jsonplaceholder.typicode.com/users/1"
  );
  return data.name;
};

module.exports = fetch;

With node modules you don't need to explicitly call jest.mock("axios").

fetch.test.js
const fetch = require("./fetch");

test("axios should be mocked", async () => {
  expect(await fetch()).toBe("Mocked name");
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionspencer.smView Question on Stackoverflow
Solution 1 - JavascriptPopsicleView Answer on Stackoverflow
Solution 2 - Javascriptspencer.smView Answer on Stackoverflow
Solution 3 - JavascriptkklView Answer on Stackoverflow
Solution 4 - JavascriptRico KahlerView Answer on Stackoverflow
Solution 5 - JavascriptniryoView Answer on Stackoverflow
Solution 6 - JavascriptAnonymous PandaView Answer on Stackoverflow