189

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.

Şivā SankĂr
  • 1,966
  • 1
  • 18
  • 34
spencer.sm
  • 19,173
  • 10
  • 77
  • 88
  • For those that follow, `jest.mock()` actually gets [hoisted](https://www.w3schools.com/js/js_hoisting.asp) like variables. As a result, they're called before the imports. – Tony May 12 '21 at 15:16

6 Answers6

286

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')

spencer.sm
  • 19,173
  • 10
  • 77
  • 88
Popsicle
  • 2,877
  • 1
  • 4
  • 3
  • This worked for me. spyOn was useless in my case, as the method is not called in my test file. – Zoman Nov 11 '20 at 10:25
  • Thank you! This solved my issue when trying to mock a single export function from a module inside node_modules – Fraudlic Apr 20 '21 at 17:37
  • 7
    This does not work anymore in Jest 27: `Spread types may only be created from object types`. – ypicard Jun 09 '21 at 13:19
  • 7
    @ypicard you have to store `jest.requireActual('./myModule')` in a variable first and then you can use the spread operator on the variable. https://jestjs.io/docs/jest-object#jestrequireactualmodulename – halshing Jun 30 '21 at 15:37
  • 4
    This didn't work for me when using an index file to store many components without a default export. I get `TypeError: Cannot read property 'default' of undefined` – Ben Gooding Jul 01 '21 at 16:10
  • 16
    It does not work for me. ```jest.mock``` and ```jest.requireActual``` works but when also try to mock one of the functions, my code keeps calling the original implementation – Kuba K Apr 13 '22 at 08:44
  • 1
    I was also getting a `Spread types may only be created from object types` error. I fixed it by casting to Module type like this: `...jest.requireActual('./modules/module') as Module`. For this, I added this import: `import Module from 'module';` – Taoufik Mohdit May 23 '22 at 15:45
  • Thank you. Thank you so much. I've been struggling with mocking `react-i18next` and its `useTranslation` method, and it was giving me nightmares with `initReactI18next` being undefined module. With this, it finally worked. Thank you so much! – Unapedra May 24 '22 at 07:38
  • What if `useContext` is invoked multiple times and I wish to use `mockReturnValueOnce` to determine the return value in each case? How can I reference the useContext mock? Trying to declare it as a variable outside the mock causes an error (" The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables") – csvan Jul 13 '22 at 19:38
  • @ypicard, you also can do "...(jest.requireActual('react') as any)" – i474232898 Jul 15 '22 at 13:21
  • 1
    Started using this approach but can't seem to get it to work. I don't seem to be able to reference to mocked function in order to determine if it has been called. Similar to @csvan problem. – Chris Parry Jul 19 '22 at 18:17
  • @BenGooding I found that if you import as usual from your index file but mock the full path to the module you want to mock it works well. ---------------------------------------------------------------------------- `import { hook1, hook2 } from '../../../../../hooks'` `jest.mock('../../../../../hooks/hook2')` – coderfin Oct 04 '22 at 12:09
  • 1
    I'm having the same issue as @KubaK. My code keeps calling the original instead of the mock given in the factory. – Connor Dooley Nov 29 '22 at 20:41
  • 1
    @Popsicle Same with me my code keeps calling the original variable, instead of mocked one. – User7723337 Mar 08 '23 at 13:38
  • You can find more info about this approach on this article, which worked for me: https://dev.to/rafaf/mock-imported-modules-in-jest-26ng – Rafa Romero Apr 25 '23 at 08:43
  • 1
    Can someone help? This doesn't seem to work anymore in 2023 – user1034912 Jul 19 '23 at 06:06
64

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

spencer.sm
  • 19,173
  • 10
  • 77
  • 88
  • 34
    This works if the function is called in my test file. But if the function is called/imported in another file it doesn't work. Any thoughts? – tanner burton Aug 25 '20 at 19:44
  • 3
    I consider this more elegant solution than require spread syntax. Moreover, you can assign spied function during `spyOn` call itself, like: `const speakSpy = jest.spyOn(utils, "speak");` and call it later: `speakSpy.mockImplementation(() => { /* stuff */ });` – Emzaw Mar 15 '21 at 12:06
  • 1
    @tannerburton It works for functions imported in other files, when combined with `jest.mock()`, see example here: https://medium.com/trabe/mocking-different-values-for-the-same-module-using-jest-a7b8d358d78b – DarthVanger May 28 '21 at 10:29
12

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]
        }
      }
    },
  })
})
Rico Kahler
  • 17,616
  • 11
  • 59
  • 85
8

For me this worked:

const utils = require('./utilities.js');
...
jest.spyOn(utils, 'speak').mockImplementation(() => jest.fn());
kkl
  • 145
  • 2
  • 7
  • 7
    Not if `speak()` isn't going to be directly called from the test suite! If the tests call a function that calls `speak()` itself, this arrangement fails! – ankush981 Mar 18 '22 at 08:30
5

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
   }
});
niryo
  • 1,275
  • 4
  • 15
  • 26
-5

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");
});
Som Shekhar Mukherjee
  • 4,701
  • 1
  • 12
  • 28
  • 3
    It's unrealistic to recommend people to copy-paste their code into mock files and replace whatever they needed. Simply put - it CANNOT scale. Changes to the real file will not be automatically reflected in the mock file, which means this introduces a lot of fragile and manual work. – Giora Guttsait Sep 05 '21 at 21:16
  • You don't need to. Just re-export everything that didn't change. `export thingToMock = jest.fn(); export { fn1, fn2, fn3 } from "../original";` PS: Keep your modules small and focused on one thing. – Jarrett Meyer Sep 08 '21 at 11:42