252

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.

1
  • For those that follow, jest.mock() actually gets hoisted like variables. As a result, they're called before the imports. Commented May 12, 2021 at 15:16

7 Answers 7

347

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

Sign up to request clarification or add additional context in comments.

18 Comments

This does not work anymore in Jest 27: Spread types may only be created from object types.
@ypicard you have to store jest.requireActual('./myModule') in a variable first and then you can use the spread operator on the variable. jestjs.io/docs/jest-object#jestrequireactualmodulename
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
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
I'm having the same issue as @KubaK. My code keeps calling the original instead of the mock given in the factory.
|
73

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

3 Comments

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?
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 */ });
@tannerburton It works for functions imported in other files, when combined with jest.mock(), see example here: medium.com/trabe/…
20

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] } } }, }) }) 

4 Comments

This is when the file creates arrow functions. They're treated at object properties not class functions I think, so don't get mocked the same way.
You are a lifesaver. 20 hours of debugging and desperation and it finally works.
This is not working anymore 2023
This is a golden solution ⭐️
9

For me this worked:

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

2 Comments

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!
Because this happening with ES import, non commonJS with require function.
7

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 } }); 

1 Comment

This didnt work for me with es6 modules. Rico's answer worked.
7

I would suggest considering that the mock be setup so that it continues to call the original until the mocks are explicitly reset and setup with the desired mock behaviour just before the test that is interested in the function being mocked.

jest.mock("fs", () => { const originalFs = jest.requireActual("fs"); return { __esModule: true, ...originalFs, // call original by default until specific tests as this avoids breaking // any imports that might perform a readFileSync when loading. readFileSync: jest.fn().mockImplementation((args) => originalFs.readFileSync(args)), }; }); 

To expand on why, most of the answers will work in most situations, however for any module from the core or an external library that is used by multiple other imports they can create a side effect.

When you use jest.mock("<name>"), it applies before imports are actually executed. Looking at https://jestjs.io/docs/mock-functions#mocking-modules you can see the mock is after the import, but it still works as expected. However in https://jestjs.io/docs/bypassing-module-mocks the mock is created before which may not be obvious that it doesn't matter, even if afterwards jest hooks in early enough to apply the mock before what is in the import is processed.

This means that anything that is imported and tries to create a concrete instance of an object to set as a module property, will get the mocked instance rather than the original.

In some cases this might be what is intended, but more likely this will be surprising. By having the mock pass through to the original call initially, it allows for the function to used by other imports initial setup and can then be switched to use the needed mock implementation before testimg the function that calls it as well.

Obviously if you are mocking something you've written this is less important, more for when working with libraries and frameworks that can have dependencies and behaviour you might not be fully aware of.

In my case I got bitten by the context object in GitHub's @actions/toolkit looking to read a JSON file on load which only caused issues when the GITHUB_EVENT_PATH env var was set. https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts

Comments

0

For ES module imported module mocking, I could be able find only one solution. You need to export the same function in 2 ways.

// '/some-module-with-foo-method.js' export const fooMethod () => ... export default { fooMethod, }; // file where you implement it: import moduleName, { fooMethod } from './some-module-with-foo-method'; // will not rewrite mock: fooMethod(); // will be able mock: moduleName.fooMethod() // in the test file, where try to mocking import moduleName from './some-module-with-foo-method'; jest .spyOn(moduleName, 'fooMethod') .mockReturnValueOnce({"message": "fooMethod mocked"}); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.