How to mock imported named function in Jest when module is unmocked

Unit TestingEcmascript 6Jestjs

Unit Testing Problem Overview


I have the following module I'm trying to test in Jest:

// myModule.js

export function otherFn() {
  console.log('do something');
}

export function testFn() {
  otherFn();

  // do other things
}

As shown above, it exports some named functions and importantly testFn uses otherFn.

In Jest when I'm writing my unit test for testFn, I want to mock the otherFn function because I don't want errors in otherFn to affect my unit test for testFn. My issue is that I'm not sure the best way to do that:

// myModule.test.js
jest.unmock('myModule');

import { testFn, otherFn } from 'myModule';

describe('test category', () => {
  it('tests something about testFn', () => {
    // I want to mock "otherFn" here but can't reassign
    // a.k.a. can't do otherFn = jest.fn()
  });
});

Any help/insight is appreciated.

Unit Testing Solutions


Solution 1 - Unit Testing

Use jest.requireActual() inside jest.mock()

> ### jest.requireActual(moduleName) > > Returns the actual module instead of a mock, bypassing all checks on whether the module should receive a mock implementation or not.

Example

I prefer this concise usage where you require and spread within the returned object:

// myModule.test.js

import { otherFn } from './myModule.js'

jest.mock('./myModule.js', () => ({
  ...(jest.requireActual('./myModule.js')),
  otherFn: jest.fn()
}))

describe('test category', () => {
  it('tests something about otherFn', () => {
    otherFn.mockReturnValue('foo')
    expect(otherFn()).toBe('foo')
  })
})

This method is also referenced in Jest's Manual Mocks documentation (near the end of Examples):

> To ensure that a manual mock and its real implementation stay in sync, it might be useful to require the real module using jest.requireActual(moduleName) in your manual mock and amending it with mock functions before exporting it.

Solution 2 - Unit Testing

Looks like I'm late to this party, but yes, this is possible.

testFn just needs to call otherFn using the module.

If testFn uses the module to call otherFn then the module export for otherFn can be mocked and testFn will call the mock.


Here is a working example:

myModule.js

import * as myModule from './myModule';  // import myModule into itself

export function otherFn() {
  return 'original value';
}

export function testFn() {
  const result = myModule.otherFn();  // call otherFn using the module

  // do other things
  
  return result;
}

myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  it('tests something about testFn', () => {
    const mock = jest.spyOn(myModule, 'otherFn');  // spy on otherFn
    mock.mockReturnValue('mocked value');  // mock the return value

    expect(myModule.testFn()).toBe('mocked value');  // SUCCESS

    mock.mockRestore();  // restore otherFn
  });
});

Solution 3 - Unit Testing

import m from '../myModule';

Does not works for me, I did use:

import * as m from '../myModule';

m.otherFn = jest.fn();

Solution 4 - Unit Testing

I know this was asked a long time ago, but I just ran into this very situation and finally found a solution that would work. So I thought I'd share here.

For the module:

// myModule.js

export function otherFn() {
  console.log('do something');
}

export function testFn() {
  otherFn();

  // do other things
}

You can change to the following:

// myModule.js

export const otherFn = () => {
  console.log('do something');
}

export const testFn = () => {
  otherFn();

  // do other things
}

exporting them as a constants instead of functions. I believe the issue has to do with hoisting in JavaScript and using const prevents that behaviour.

Then in your test you can have something like the following:

import * as myModule from 'myModule';


describe('...', () => {
  jest.spyOn(myModule, 'otherFn').mockReturnValue('what ever you want to return');

  // or

  myModule.otherFn = jest.fn(() => {
    // your mock implementation
  });
});

Your mocks should now work as you would normally expect.

Solution 5 - Unit Testing

The transpiled code will not allow babel to retrieve the binding that otherFn() is referring to. If you use a function expession, you should be able to achieve mocking otherFn().

// myModule.js
exports.otherFn = () => {
  console.log('do something');
}

exports.testFn = () => {
  exports.otherFn();

  // do other things
}

 

// myModule.test.js
import m from '../myModule';

m.otherFn = jest.fn();

But as @kentcdodds mentioned in the previous comment, you probably would not want to mock otherFn(). Rather, just write a new spec for otherFn() and mock any necessary calls it is making.

So for example, if otherFn() is making an http request...

// myModule.js
exports.otherFn = () => {
  http.get('http://some-api.com', (res) => {
    // handle stuff
  });
};

Here, you would want to mock http.get and update your assertions based on your mocked implementations.

// myModule.test.js
jest.mock('http', () => ({
  get: jest.fn(() => {
    console.log('test');
  }),
}));

Solution 6 - Unit Testing

Basing on Brian Adams' answer this is how I was able to use the same approach in TypeScript. Moreover, using jest.doMock() it's possible to mock module functions only in some specific tests of a test file and provide an individual mock implementations for each of them.

src/module.ts

import * as module from './module';

function foo(): string {
  return `foo${module.bar()}`;
}

function bar(): string {
  return 'bar';
}

export { foo, bar };

test/module.test.ts

import { mockModulePartially } from './helpers';

import * as module from '../src/module';

const { foo } = module;

describe('test suite', () => {
  beforeEach(function() {
    jest.resetModules();
  });

  it('do not mock bar 1', async() => {
    expect(foo()).toEqual('foobar');
  });

  it('mock bar', async() => {
    mockModulePartially('../src/module', () => ({
      bar: jest.fn().mockImplementation(() => 'BAR')
    }));
    const module = await import('../src/module');
    const { foo } = module;
    expect(foo()).toEqual('fooBAR');
  });

  it('do not mock bar 2', async() => {
    expect(foo()).toEqual('foobar');
  });
});

test/helpers.ts

export function mockModulePartially(
  modulePath: string,
  mocksCreator: (originalModule: any) => Record<string, any>
): void {
  const testRelativePath = path.relative(path.dirname(expect.getState().testPath), __dirname);
  const fixedModulePath = path.relative(testRelativePath, modulePath);
  jest.doMock(fixedModulePath, () => {
    const originalModule = jest.requireActual(fixedModulePath);
    return { ...originalModule, ...mocksCreator(originalModule) };
  });
}

Mocking functions of a module is moved to helper function mockModulePartially located in a separate file so it can be used from different test files (which, in common, can be located in other directories). It relies on expect.getState().testPath to fix path to a module (modulePath) being mocked (make it relative to helpers.ts containing mockModulePartially). mocksCreator function passed as a second argument to mockModulePartially should return mocks of the module. This function receives originalModule and mock implementations can optionally rely on it.

Solution 7 - Unit Testing

I solved my problem with a mix of the answers that I found here:

myModule.js

import * as myModule from './myModule';  // import myModule into itself

export function otherFn() {
  return 'original value';
}

export function testFn() {
  const result = myModule.otherFn();  // call otherFn using the module

  // do other things

  return result;
}

myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  let otherFnOrig;
  
  beforeAll(() => {
    otherFnOrig = myModule.otherFn;
    myModule.otherFn = jest.fn();
  });
  
  afterAll(() => {
    myModule.otherFn = otherFnOrig;
  });

  it('tests something about testFn', () => {
    // using mock to make the tests
  });
});

Solution 8 - Unit Testing

On top of the first answer here, you can use babel-plugin-rewire to mock imported named function too. You can check out the section superficially for named function rewiring.

One of the immediate benefits for your situation here is that you do not need to change how you call the other function from your function.

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
QuestionJon RubinsView Question on Stackoverflow
Solution 1 - Unit TestinggfullamView Answer on Stackoverflow
Solution 2 - Unit TestingBrian AdamsView Answer on Stackoverflow
Solution 3 - Unit TestingbobuView Answer on Stackoverflow
Solution 4 - Unit TestingJack KinseyView Answer on Stackoverflow
Solution 5 - Unit TestingvutranView Answer on Stackoverflow
Solution 6 - Unit TestingezzeView Answer on Stackoverflow
Solution 7 - Unit TestingdemaroarView Answer on Stackoverflow
Solution 8 - Unit Testingschrodinger's codeView Answer on Stackoverflow