Jon Rubins
Jon Rubins

Reputation: 4413

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

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.

Upvotes: 207

Views: 240009

Answers (8)

bobu
bobu

Reputation: 945

import m from '../myModule';

Does not work for me, I did this instead:

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

m.otherFn = jest.fn();

Upvotes: 43

gfullam
gfullam

Reputation: 12045

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.

Upvotes: 225

ezze
ezze

Reputation: 4110

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.

Upvotes: 5

demaroar
demaroar

Reputation: 167

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

Upvotes: 2

Jack Kinsey
Jack Kinsey

Reputation: 818

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.

Upvotes: 22

Brian Adams
Brian Adams

Reputation: 45840

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

Upvotes: 68

schrodinger&#39;s code
schrodinger&#39;s code

Reputation: 2734

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.

Upvotes: 0

vutran
vutran

Reputation: 887

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

Upvotes: 11

Related Questions