201

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.

Jon Rubins
  • 4,323
  • 9
  • 32
  • 51
  • 9
    I wouldn't do this. Mocking is generally not something you want to do anyway. And if you need to mock something (due to making server calls/etc.) then you should just extract `otherFn` into a separate module and mock that. – kentcdodds Sep 28 '16 at 19:19
  • 3
    I'm also testing with the same approach @jrubins uses. Test behaviour of `function A` who calls `function B` but I don't want to execute the real implementation of `function B` because I want to just test the logic implemented in `function A` – jplaza Oct 14 '16 at 20:59
  • 80
    @kentcdodds, Could you clarify what you mean by "Mocking is generally not something you want to do anyway."? That seems to be a fairly broad (overly broad?) statement, as mocking is certainly something that is often used, presumably for (at least some) good reasons. So, are you perhaps referring to why mocking might not be good _here_, or do you really mean in general? – Andrew Willems Dec 13 '16 at 22:32
  • 7
    Often mocking is testing implementation details. Especially at this level it leads to tests that aren't really validating much more than the fact that your tests work (not that your code works). – kentcdodds Dec 14 '16 at 05:46
  • 1
    I found that Jest's mocking functionality struggles in cases like this, especially when TypeScript is also involved. Therefore, I recommend to use sinon.js, which is a standalone mock/stub framework: https://sinonjs.org/releases/v7.3.2/stubs/. It works pretty well together with Jest. – HelloWorld101 Jul 03 '19 at 13:40
  • This is discussed in a Jest issue on GH. https://github.com/facebook/jest/issues/936#issuecomment-545080082 – Nickofthyme Oct 22 '19 at 17:59
  • 6
    I was about to call that first commenter an idiot before I realized who it was. Very confused by that comment, though. First off, mocking is essential to testing modern applications. Also, I have the same situation and it would make no sense to separate the functions into separate modules. – paulwithap Oct 25 '19 at 19:23
  • 4
    @kentcdodds So, if my component makes an api call, how can I _not_ mock that to test the output of the render? I don't think mocking an api call is testing implementation details. For example, if my api call fills user data on a page, the _output_ of my component is the page with the data, so I can't really test without it. Right? I'm still testing outputs with inputs (the userId prop, for example). I'm not testing _whether_ the api call was made or not. Or am I completely misunderstanding? – Yatrix Nov 19 '19 at 19:26
  • 3
    There's too much nuance for a Stack Overflow comment. But I've written about this: https://kentcdodds.com/blog/the-merits-of-mocking and https://kentcdodds.com/blog/testing-implementation-details – kentcdodds Nov 19 '19 at 23:06
  • 5
    For the record, since writing this question years ago, I have since changed my tune on how much mocking I'd like to do (and don't do mocking like this anymore). These days I very much agree with @kentcdodds and his testing philosophy (and highly recommend his blog and `@testing-library/react` for any Reacters out there) but I know this is a contentious subject. – Jon Rubins Dec 02 '19 at 17:08
  • @AndrewWillems "... as mocking is certainly something that is often used, presumably for (at least some) good reasons" Conventional wisdom can often be dangerously bad, so the appeal to authority you're making here is not helpful. And mocking is certainly something that in most cases leads to poor tests, because mocks lead to tests that don't actually test anything. – Sámal Rasmussen May 03 '23 at 09:12

8 Answers8

219

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.

Patrick
  • 6,495
  • 6
  • 51
  • 78
gfullam
  • 11,531
  • 5
  • 50
  • 64
  • 6
    Fwiw, you can make this even more concise by removing the `return` statement and wrapping the arrow function body in parentheses: eg. `jest.mock('./myModule', () => ({ ...jest.requireActual('./myModule'), otherFn: () => {}}))` – Nick F Apr 29 '19 at 11:10
  • Is this even possible to have jest.mock () in this solution inside of the test definition and not on the import statements level (top level in the file)? – denu Oct 18 '19 at 09:32
  • This is done at the top of the file before the first describe statement. – gfullam Oct 22 '19 at 14:52
  • 2
    Not working for me: jest.mock('../../src/helpers', () => ({ ...jest.requireActual('../../src/helpers'), getCurrentMessage: (): { getFrom: () => string } => { return { getFrom: (): string => 'Test User ', }; }, })); The function I'm testing is still calling the actual `getCurrentMessage`. – paulwithap Oct 25 '19 at 19:34
  • 2
    `...jest.requireActual` didn't work for me properly because I have path-aliasing using babel.. Works either with `...require.requireActual` or after removing aliasing from path – Tzahi Leh Oct 27 '19 at 09:53
  • 1
    How would you test that `otherFun` was called in this case? Assuming `otherFn: jest.fn()` – Stevula Sep 29 '20 at 18:46
  • 1
    @Stevula I have updated my answer to show a real usage example. I show the `mockReturnValue` method to better demonstrate that the mocked version is getting called instead of the original, but if you really just want to see if it has been called without asserting against the return value, you can use the jest matcher [`.toHaveBeenCalled()`](https://jestjs.io/docs/en/expect#tohavebeencalled). – gfullam Nov 05 '20 at 16:10
  • 14
    This answer actually doesn't answer the question. Provided solution doesn't call `testFn` that in turn should call a mock of `otherFn`. If you call `testFn` it will still use original `otherFn`. At least, it doesn't work for me in TypeScript. See the [explaination](https://github.com/facebook/jest/issues/936#issuecomment-214939935). – ezze Dec 10 '20 at 23:49
  • @ezze Good callout. I mistakenly did not provide a solution for that important aspect. Upon trying to do so, I am experiencing the problem. I'm investigating. – gfullam Dec 11 '20 at 17:17
  • @gfullam You can check [Brian Adams' answer](https://stackoverflow.com/a/52770749/506695) and [my answer](https://stackoverflow.com/a/65243744/506695) based on it and adapted for TypeScript. It looks like as some kind of trick but unfortunatelly I didn't find any other solution. – ezze Dec 11 '20 at 20:21
  • Note that you can spy on the method without altering it's functionality by doing `otherFn: jest.fn(jest.requireActual('./myModule.js').otherFn)` – Klas Mellbourn Feb 16 '21 at 11:24
  • How can i reset this mock so that other tests can use the original unmocked version? – Aeternus Mar 24 '23 at 17:41
61

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
  });
});
Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • 5
    This is essentially an ES6 version of the approach used at Facebook and described by a Facebook dev in the middle of [this post](https://github.com/facebook/jest/issues/936#issuecomment-214939935). – Brian Adams Oct 12 '18 at 00:55
  • 1
    instead of importing myModule into itself, just call `exports.otherFn()` – andrhamm Nov 02 '18 at 14:34
  • 3
    @andrhamm `exports` doesn't exist in ES6. Calling `exports.otherFn()` works right now because ES6 is being compiled to an earlier module syntax, but it will break when ES6 is supported natively. – Brian Adams Nov 02 '18 at 16:24
  • Having this exact problem right now and I'm sure I've encountered this problem before. I've had to remove a load of exports. to help tree shaking along and it's broken many tests. I'll see if this has any effect, but it seems so hacky. I've come across this problem a number of times and like other answers have said, things like babel-plugin-rewire or even better, https://www.npmjs.com/package/rewiremock which I'm fairly sure can do the above too. – Astridax Nov 02 '18 at 16:58
  • Is it possible to instead of mock a return value, mock a throw? Edit: you can, here is how https://stackoverflow.com/a/50656680/2548010 – Big Money Aug 27 '19 at 22:44
  • If you're using Babel, see also: https://www.npmjs.com/package/babel-plugin-explicit-exports-references – Xunnamius Dec 21 '21 at 16:23
40
import m from '../myModule';

Does not work for me, I did this instead:

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

m.otherFn = jest.fn();
Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
bobu
  • 908
  • 8
  • 14
  • 7
    How would you restore otherFn's original functionality after the test so that it does not interfere with other testS? – Aequitas Nov 20 '17 at 00:58
  • 1
    I think you can configure jest to clear mocks after every test? From the docs: "The clearMocks configuration option is available to clear mocks automatically between tests.". You can set `clearkMocks: true` jest package.json config. https://facebook.github.io/jest/docs/en/mock-function-api.html – Cole Jun 01 '18 at 17:44
  • 2
    if this is such problem to change global state you can always store original functionality inside some kind of test variable and bring back it after test – bobu Jun 08 '18 at 06:32
  • 1
    const original; beforeAll(() = > { original = m.otherFn; m.otherFn = jest.fn(); }) afterAll(() => { m.otherFn = original; }) it should work, however I didn't test it – bobu Jun 08 '18 at 06:32
16

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.

Jack Kinsey
  • 718
  • 1
  • 8
  • 20
  • I'm stuck with the same problem now and I'm wondering how would you mock `otherFn` if, instead of being a function, it would be an array? So `testFn` is reading an array that is also exported from the module and I'd like to mock that array in my tests to change the behaviour of `testFn` without mocking itself. – nilfalse Jan 16 '21 at 19:02
11

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');
  }),
}));
vutran
  • 887
  • 8
  • 19
  • 1
    what if otherFn and testFn is used by several other modules? would you need to set up the http mock in all the test files which uses (however deep the stack) those 2 modules? Also, if you already have a test for testFn, why not stub testFn directly instead of http in the modules that uses testFn? – ricmed Jan 14 '17 at 21:03
  • 2
    so if `otherFn` is broken, will fail all tests that depends on that one. Also if `otherFn` has 5 ifs inside you might need to test that your `testFn` is working fine for all those sub cases. You will have so many more code paths to test now. – Totty.js Oct 21 '18 at 04:52
5

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.

ezze
  • 3,933
  • 2
  • 34
  • 58
2

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
  });
});
demaroar
  • 127
  • 1
  • 4
0

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.

schrodinger's code
  • 2,624
  • 1
  • 25
  • 19