Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to mock specific module function? #936

Closed
seibelj opened this issue Apr 25, 2016 · 123 comments
Closed

How to mock specific module function? #936

seibelj opened this issue Apr 25, 2016 · 123 comments

Comments

@seibelj
Copy link

seibelj commented Apr 25, 2016

I'm struggling with something that I think should be both easy and obvious, but for whatever reason I can't figure it out.

I have a module. It exports multiple functions. Here is myModule.js:

export function foo() {...}
export function bar() {...}
export function baz() {...}

I unmock the module for testing.

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

However, I need to mock foo, because it makes ajax calls to my backend. I want every function in this file to remain unmocked, expect for foo, which I want to be mocked. And the functions bar and baz make calls internally to foo, so when my test calls bar(), the unmocked bar will call the mocked foo.

It appears in the jest documentation that calls to unmock and mock operate on the entire module. How can I mock a specific function? Arbitrarily breaking up my code into separate modules so they can be tested properly is ridiculous.

@seibelj
Copy link
Author

seibelj commented Apr 25, 2016

Upon deeper analysis, it appears that jest-mock generates an AST for the whole module, then uses that AST to create a mocked module that conforms to original's exports: https://github.com/facebook/jest/tree/master/packages/jest-mock

Other testing frameworks, such as Python's mock (https://docs.python.org/3/library/unittest.mock-examples.html), let you mock specific functions. This is a fundamental testing concept.

I highly recommend the ability to mock a portion of a module. I think that jest-mock should be changed to conditionally ignore exports from mocking, and reference the original implementation.

@cpojer
Copy link
Member

cpojer commented Apr 25, 2016

You can do:

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

const myModule = require('myModule');
myModule.foo = jest.fn();

See http://facebook.github.io/jest/docs/api.html#mock-functions

@cpojer cpojer closed this as completed Apr 25, 2016
@seibelj
Copy link
Author

seibelj commented Apr 26, 2016

I think you have a fundamental misunderstanding of how require works. When you call require(), you don't get an instance of the module. You get an object with references to the module's functions. If you overwrite a value in the required module, your own reference is overwritten, but the implementation keeps the original references.

In your example, if you call myModule.foo(), yes, you will call the mocked version. But if you call myModule.bar(), which internally calls foo(), the foo it references is not your overwritten version. If you don't believe me, you can test it out.

Therefore, the example you described is inadequate for the problem I have. Do you know something I don't?

@seibelj
Copy link
Author

seibelj commented Apr 26, 2016

@cpojer

@cpojer
Copy link
Member

cpojer commented Apr 27, 2016

I do believe I understand this quite well. The way babel compiles modules however doesn't make this easier to understand and I understand your confusion. I don't know exactly how this would behave in a real ES2015 environment with modules, mainly because no such environment exists right now (except maybe latest versions of Chrome Canary, which I haven't tried yet). In order to explain what happens, we have to look at the compiled output of your babel code. It will look something like this:

var foo = function foo() {};
var bar = function bar() { foo(); };

exports.foo = foo;
exports.bar = bar;

In this case, it is indeed correct that you cannot mock foo and I apologize for not reading your initial issue correctly, however it did not make any assumption on how foo was called, so I assumed it was exports.foo(). Supporting the above by mocking a function after requiring a module is impossible in JavaScript – there is (almost) no way to retrieve the binding that foo refers to and modify it.

However, if you change your code to this:

var foo = function foo() {};
var bar = function bar() { exports.foo(); };

exports.foo = foo;
exports.bar = bar;

and then in your test file you do:

var module = require('../module');
module.foo = jest.fn();
module.bar();

it will work just as expected. This is what we do at Facebook where we don't use ES2015.

While ES2015 modules may have immutable bindings for what they export, the underlying compiled code that babel compiles to right now doesn't enforce any such constraints. I see no way currently to support exactly what you are asking in a strict ES2015 module environment with natively supported modules. The way that jest-mock works is that it runs the module code in isolation and then retrieves the metadata of a module and creates mock functions. Again, in this case it won't have any way to modify the local binding of foo. If you do have ideas on how to effectively implement this, please contribute here or with a pull request. I'd like to remind you that we have a code of conduct for this project that you can read up on here: https://code.facebook.com/pages/876921332402685/open-source-code-of-conduct

The right solution in your example is not to mock foo but to mock the higher-level API that foo is calling (such as XMLHttpRequest or the abstraction that you use instead).

@seibelj
Copy link
Author

seibelj commented Apr 28, 2016

@cpojer Thank you for your detailed explanation. I'm sorry if I offended you with my language, I am very efficient with my engineering writing and I want to get my point across ASAP. To put things into perspective, I spent 5 hours of time trying to understand this issue and wrote 2 detailed comments, then you closed it with a brief message that completely missed the point of both of my statements. That is why my next message said you had a "fundamental misunderstanding", because either 1) you did not understand the point I was making, or 2) you didn't understand require(), which thankfully was option 1.

I will ponder a possible solution to my problem, to get around it for now I mocked a lower level API, but there should definitely be a way to mock the function directly, as that would be quite useful.

@cpojer
Copy link
Member

cpojer commented May 9, 2016

I agree it would be useful to be able to do this, but there isn't a good way in JS to do it without (probably slow) static analysis upfront :(

@lnhrdt
Copy link
Contributor

lnhrdt commented Oct 9, 2016

@cpojer: I'm unsure if jumping in here 5 months later is the way to go but I couldn't find any other conversations about this.

Jumping off from your suggestion above, I've done this to mock out one function from another in the same module:

jest.unmock('./someModule.js');
import someModule from './someModule.js';

it('function1 calls function 2', () => {
    someModule.function2 = jest.fn();

    someModule.function1(...);

    expect(someModule.function2).toHaveBeenCalledWith(...);
});

This works for the one test, but I haven't found a way to accomplish this in a way that's isolated to just the one it(...); block. As written above, it affects every test which makes it hard to test the real function2 in another test. Any tips?

@cpojer
Copy link
Member

cpojer commented Oct 10, 2016

You can call .mockClear on the function in beforeEach or call jest.clearAllMocks() if you are using Jest 16.

@lnhrdt
Copy link
Contributor

lnhrdt commented Oct 11, 2016

Hey @cpojer! I am using Jest 16. Neither jest.clearAllMocks() or someModule.function2.mockClear() work for me. They only work when the mock is an entire module, not a function of an imported module. In my project, the function remains mocked in subsequent tests. If this isn't expected I'll see if I can replicate in a small sample project and create a new issue. Good idea?

@yanivefraim
Copy link

yanivefraim commented Nov 20, 2016

@cpojer -

The right solution in your example is not to mock foo but to mock the higher-level API that foo is calling (such as XMLHttpRequest or the abstraction that you use instead).

I'm new to Jest and I'm struggling with a similar problem. I'm using axios, which under the hood uses XMLHttpRequest, and I do not want to mock axios, but to mock the actual XMLHttpRequest. It seems that I would have to implement its methods by myself, something like this. Is this the right approach?

Thanks!

@cpojer
Copy link
Member

cpojer commented Nov 21, 2016

yeah, something like this should get you on the right path! :) Use jest.fn as a nicer API, though :D

@sorahn
Copy link

sorahn commented Nov 21, 2016

@cpojer regarding your comment here: #936 (comment)

How would you do that with ES2015?

// myModyle.js
export foo = (string) => "foo-" + string
export bar = (string2) => foo(string2)

// myModule.test.js
var module = require('./myModule');

// how do I mock foo here so this test passes?
expect(bar("hello")).toEqual("test-hello")

@ainesophaur
Copy link

For anyone who comes across this looking for a solution, the following seems to be working for me when exporting many const/functions in one file, and importing them in a file which I'm testing

function mockFunctions() {
  const original = require.requireActual('../myModule');
  return {
    ...original, //Pass down all the exported objects
    test: jest.fn(() => {console.log('I didnt call the original')}),
    someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}),
  }
jest.mock('../myModule', () => mockFunctions());
const storage = require.requireMock('../myModule');
`

@huyph
Copy link

huyph commented Apr 13, 2017

@ainesophaur, not sure what I am doing wrong here. But it seems it doesn't work
I am currently on jest 18.1 (and create-react-app 0.9.4)

...<codes from comment above>..

// Let's say the original myModule has a function doSmth() that calls test()
storage.doSmth();
expect(storage.test).toHaveBeenCalled();

Test will then fail with:

expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called.

@ainesophaur
Copy link

@huyph you'd have to mock your doSmth method and your test method in order for jest to test whether it was called. If you can provide the snippet of your mocking code I can check what's wrong

@huyph
Copy link

huyph commented Apr 17, 2017

@ainesophaur ...uhm. I thought your codes above are for mocking the test() method? this part: test: jest.fn(() => {console.log('I didnt call the original')}),

@rantonmattei
Copy link

@ainesophaur I tried your code as well. But it did not work for me. It never executes the mock function. So, the expectation is never met.

I think this is inherent to the way require works like stated above... I wish there was a solution for this.

@cpojer Is there anything new regarding partially mocking modules?

@ainesophaur
Copy link

@rantonmattei & @huyph I'd have to see a snippet of your mock definitions and the test you're running. You have to define your mock before the actual implementation file is required/imported. Its been a while since I've worked with JEST, but I do remember I eventually got it to mock everything I needed to, whether it was a node_modules library or a file in my app. I'm a bit short on time ATM, but here is some of the tests from a project I worked on using Jest.

Mocking a file from a dependency

Actual function definition in this example is done by react-native.. I'm mocking the file "react-native/Libraries/Utilities/dismissKeyboard.js"

This is a mock file under __mocks__/react-native/Libraries/Utilities/dismissKeyboard.js

function storeMockFunctions() {
  return jest.fn().mockImplementation(() => true);
}
jest.mock('react-native/Libraries/Utilities/dismissKeyboard', () => storeMockFunctions(), { virtual: true });
const dismissKeyboard = require('react-native/Libraries/Utilities/dismissKeyboard');
exports = module.exports = storeMockFunctions;

I can't find the test file I used for the above, but it was something like require the module, jest would find it in mocks and then I could do something like

expect(dismissKeyboard.mock.calls).toHaveLength(1);

Mocking a file you control
Actual function definition

export const setMerchantStores = (stores) => storage.set('stores', stores);

Test file with mock

const { storeListEpic, offerListEpic } = require('../merchant');

function storeMockFunctions() {
  const original = require.requireActual('../../common/Storage');
  return {
    ...original,
    setMerchantStores: jest.fn((...args) => original.setMerchantStores(...args)),
    setMerchantOffers: jest.fn((...args) => original.setMerchantOffers(...args)),
  };
}
jest.mock('../../common/Storage', () => storeMockFunctions());
import * as storage from '../../common/Storage';

afterEach(() => {
  storage.setMerchantStores.mockClear();
});

it('handle storeListEpic type STORE_LIST_REQUEST -> STORE_LIST_SUCCESS', async () => {
  const scope = nock('http://url')
  .get('/api/merchant/me/stores')
  .reply(200, storeData);
  const result = await storeListEpic(ActionsObservable.of(listStores())).toPromise();
  expect(storage.setMerchantStores.mock.calls).toHaveLength(1);
  expect(await storage.getMerchantStores()).toEqual({ ids: storesApiData.result, list: storesApiData.entities.store});
});

@huyph
Copy link

huyph commented May 5, 2017

thanks for sharing @ainesophaur. I still can't get it to work with jest 18.1. Here are my codes:

it('should save session correctly', () => {

  function mockFunctions() {
    const original = require.requireActual('./session');
    return {
      ...original,
      restartCheckExpiryDateTimeout: jest.fn((() => {
        console.log('I didn\'t call the original');
      })),
    }
  }

  jest.mock('./session', () => mockFunctions());
  const mockSession = require('./session');

  // NOTE: saveSession() will call the original restartCheckExpiryDateTimeout() instead of my
  // mock one. However, mockSession.restartCheckExpiryDateTimeout() does call the mock one
  mockSession.saveSession('', getTomorrowDate(), 'AUTH');

  // mockSession.restartCheckExpiryDateTimeout(); // does print out "I didn't call the original"

  expect(mockSession.restartCheckExpiryDateTimeout).toHaveBeenCalled();
});

In session.js

export function saveSession(sessionId, sessionExpiryDate, authToken) {
  ....
  restartCheckExpiryDateTimeout(sessionExpiryDate);
  ...
}
....

export function restartCheckExpiryDateTimeout(...) {
....
}

I can't find a way to resolve this issue. Can I reopen this please? @cpojer

@ainesophaur
Copy link

@huyph the way you're doing the export saveSession is going to call the locally defined restartCheckExpiryDateTimeout instead of going through the module and calling module.restartCheckExpiryDateTimeout -- thus your mocked module.restartCheckExpiryDateTimeout wont be detected by saveSession as saveSession is calling the actual defined restartCheckExpiryDateTimeout function.

I'd assign saveSession to a const and then do saveSession.restartCheckExpiryDateTimeout = () => {...logic}. .then from within saveSession.saveSession, call saveSession.restartCheckExpiryDateTimeout instead of just restartCheckExpiryDateTimeout. Export your new const instead of the actual function saveSession which then defines your methods. Then when you call your someVar.saveSession() it'll then internally call saveSession.restartCheckExpiryDateTimeout() which is now mocked.

@huyph
Copy link

huyph commented May 9, 2017

I should have added that restartCheckExpiryDateTimeout() is an exported function. Not a locally defined function within saveSession()... (Updated my comment above). In that case, I think module.saveSession() should call the right module.restartCheckExpiryDateTimeout()which is mocked.

But I will give what you suggest above a go though. Moving both saveSession() and restartCheckExpiryDateTimeout() to another const. Thanks

@ainesophaur
Copy link

ainesophaur commented May 9, 2017 via email

@huyph
Copy link

huyph commented May 9, 2017

Just tried that..I found that:

This does NOT work: (i.e the original restartCheckExpiryDateTimeout() is still called)

export session = {
   saveSession: () => {
      session.restartCheckExpiryDateTimeout();
   },
   restartCheckExpiryDateTimeout: () => {},
}

This works: (i.e the mock restartCheckExpiryDateTimeout() is called instead). The difference is the use of function() instead of arrow form, and the use of this. instead of session.

export session = {
   saveSession: function() {
      this.restartCheckExpiryDateTimeout();
   },
   restartCheckExpiryDateTimeout: () => {},
}

It could be a problem with jest transpiling these codes ....

@ainesophaur
Copy link

ainesophaur commented May 9, 2017 via email

@mrdulin
Copy link

mrdulin commented May 31, 2017

@sorahn same issue. es6 + babel , How to mock?
@cpojer Is that means es6 + babel , export const function xx() {} , export many function , Jest has no way to mock a function in a module(file) called by other function in the same module(file)? I test it, it seems I am correct. Just only for commonjs pattern, Jest can mock the function successfully, Like your example.

@mrdulin
Copy link

mrdulin commented May 31, 2017

@ainesophaur not working.

module:

export const getMessage = (num: number): string => {
  return `Her name is ${genName(num)}`;
};

export function genName(num: number): string {
  return 'novaline';
}

test:

function mockFunctions() {
  const original = require.requireActual('../moduleA');
  return {
    ...original,
    genName: jest.fn(() => 'emilie')
  }
}
jest.mock('../moduleA', () => mockFunctions());
const moduleA = require('../moduleA');

describe('mock function', () => {

  it('t-0', () => {
    expect(jest.isMockFunction(moduleA.genName)).toBeTruthy();
  })
  
  it('t-1', () => {
    
    expect(moduleA.genName(1)).toBe('emilie');
    expect(moduleA.genName).toHaveBeenCalled();
    expect(moduleA.genName.mock.calls.length).toBe(1);
    expect(moduleA.getMessage(1)).toBe('Her name is emilie');
    expect(moduleA.genName.mock.calls.length).toBe(2);

  });

});

test result:

FAIL  jest-examples/__test__/mock-function-0.spec.ts
  ● mock function  t-1

    expect(received).toBe(expected)

    Expected value to be (using ===):
      "Her name is emilie"
    Received:
      "Her name is novaline"

      at Object.it (jest-examples/__test__/mock-function-0.spec.ts:22:35)
      at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)

  mock function
    ✓ t-0 (1ms)
    ✕ t-1 (22ms)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        0.215s, estimated 1s

@ainesophaur
Copy link

ainesophaur commented May 31, 2017 via email

@huyph
Copy link

huyph commented Jun 22, 2017

@ainesophaur : I attempted export class Session { }. And it doesn't work for me.

The only approach that works for me is in my comment above: where function syntax is used instead of arrow () =>. Here:

export const session = {
   saveSession: function() {
      this.restartCheckExpiryDateTimeout();
   },
   restartCheckExpiryDateTimeout: () => {},
}

This is on Jest 20.0.3

@nickofthyme
Copy link
Contributor

nickofthyme commented Jan 11, 2020

@johncmunson That's a good point. However, this example you showed of mocking a module only works if you only need to run jest.mock once and none of the mocked method use another export from that module.

Take the example from above...I've added bar to show how I want to mock the module differently between foo and bar.

export const message = (): string => {
  return 'Hello world';
}

export const foo = (): string => {
  return message();
}

export const bar = (): (() => string) => {
  return foo;
}

Using jest.mock with jest.requireActual I think would go something like this.

import * as mockTestModule from './hello';

jest.mock('./hello');
const actualTestModule = jest.requireActual('./hello');

describe('test hello', function () {
  afterAll(() => {
    jest.restoreAllMocks();
  });

  // first test doesn't depend on any other method of the module so no mocks
  it('should NOT mock message in foo', function () {
    const actual = actualTestModule.foo();

    expect(actual).toBe('Hello world');
  });

  // the second I want to mock message in foo
  it('should mock message in foo', function () {
    jest.spyOn(mockTestModule, 'message').mockReturnValue('my message');
    const actual = actualTestModule.foo();

    expect(actual).toBe('my message'); // fails
    expect(mockTestModule.message).toHaveBeenCalledTimes(1); // never called
  });

  it('should mock foo in bar', function () {
    jest.spyOn(mockTestModule, 'foo').mockReturnValue('my message');
    const actual = actualTestModule.bar();

    expect(actual()).toBe('my message'); // fails
    expect(mockTestModule.message).toHaveBeenCalledTimes(1); // never called
  });
});

I even tried mocking them separately with jest.doMock and still got the same result.

Click to see code
import * as testModule from './hello';

describe('test hello', function () {
  afterAll(() => {
    jest.restoreAllMocks();
  });

  it('should NOT mock message in foo', function () {
    const actual = testModule.foo();

    expect(actual).toBe('Hello world');
  });

  it('should mock message in foo', function () {
    jest.doMock('./hello', () => {
      // Require the original module to not be mocked...
      const originalModule = jest.requireActual('./hello');

      return {
        ...originalModule,
        message: jest.fn().mockReturnValue('my message'),
      };
    });
    const actual = testModule.foo();

    expect(actual).toBe('my message'); // fails
    expect(testModule.message).toHaveBeenCalledTimes(1); // never called
  });

  it('should mock foo in bar', function () {
    jest.doMock('./hello', () => {
      // Require the original module to not be mocked...
      const originalModule = jest.requireActual('./hello');

      return {
        ...originalModule,
        foo: jest.fn().mockReturnValue('my message'),
      };
    });
    const actual = testModule.bar()();

    expect(actual).toBe('my message'); // fails
    expect(testModule.foo).toHaveBeenCalledTimes(1); // never called
  });
});

The issue with this approach is that requiring the actual module, then say calling foo, still calls the actual message function and not the mock.

I wish it were this simple but from what I see this does not help for the examples from this thread. If I am missing something here please let me know. I'll gladly admit fault.

@gempain
Copy link

gempain commented Jan 16, 2020

For anyone who comes across this looking for a solution, the following seems to be working for me when exporting many const/functions in one file, and importing them in a file which I'm testing

function mockFunctions() {
  const original = require.requireActual('../myModule');
  return {
    ...original, //Pass down all the exported objects
    test: jest.fn(() => {console.log('I didnt call the original')}),
    someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}),
  }
jest.mock('../myModule', () => mockFunctions());
const storage = require.requireMock('../myModule');
`

The following works and is a bit shorter:

const module = require('./module');
jest.spyOn(module, 'myFn').mockImplementation(() => 'val');

In Typescript, just import instead of require:

import * as module from './module';

This has the advantage of making life easy to restore original functions and clear mocks.

@jeffryang24
Copy link

For anyone who comes across this looking for a solution, the following seems to be working for me when exporting many const/functions in one file, and importing them in a file which I'm testing

function mockFunctions() {
  const original = require.requireActual('../myModule');
  return {
    ...original, //Pass down all the exported objects
    test: jest.fn(() => {console.log('I didnt call the original')}),
    someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}),
  }
jest.mock('../myModule', () => mockFunctions());
const storage = require.requireMock('../myModule');
`

The following works and is a bit shorter:

const module = require('./module');
jest.spyOn(module, 'myFn').mockImplementation(() => 'val');

In Typescript, just import instead of require:

import * as module from './module';

This has the advantage of making life easy to restore original functions and clear mocks.

Oh, yes also this method is not working if your object only has getter defined. The error message could be like below:

Test suite failed to run

    TypeError: Cannot set property useContent of #<Object> which has only a getter

Probably need to use jest.mock(..) for this case. 🙇‍♂️

@bitttttten
Copy link

My mocks are working by using the following:

import { unsubscribe } from "../lib/my-lib"
import { MyComponent } from "./index"

test("unsubscribe gets called", () => {
	const module = require("../lib/my-lib")
	jest.spyOn(
		module,
		"unsubscribe"
	).mockImplementation(() => jest.fn())

	const { getByTestId } = render(() => <MyComponent  />)

	let button = getByTestId("trigger.some.action.button")

	fireEvent.press(button)

	expect(unsubscribe).toHaveBeenCalled()
})

It doesn't seem so elegant and doesn't scale so easily, it works very well though and suits my cases for now.. but if anybody can suggest any other syntax that would be great! This is the only syntax that seems to be working.

@mrdulin
Copy link

mrdulin commented Apr 10, 2020

es6 module code:

export const funcA = () => {};
export const funcB = () => {
  funcA();
};

After transpiled to CommonJS:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.funcB = exports.funcA = void 0;

var funcA = function funcA() {};

exports.funcA = funcA; // You can mock or add a spy  on this `funcA`

var funcB = function funcB() {
  funcA();  // This is still original `funcA`
};

exports.funcB = funcB;

There are many ways to solve this situation.

  1. You need to change the code like this, so you can use the mocked/spied funcA
function funcA() {}
exports.funcA = funcA;

function funcB() {
  exports.funcA(); // Now, this `exports.funcA` is added a spy or mocked. Keep the same reference to `funcA`
}
exports.funcB = funcB;

Or,

export let funcA = () => {};
export const funcB = () => {
  exports.funcA();
};

unit test results:

 PASS  myModule.test.ts (9.225s)
  funcB
    ✓ should call funcA (3ms)

-------------|---------|----------|---------|---------|-------------------
File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------|---------|----------|---------|---------|-------------------
All files    |     100 |      100 |     100 |     100 |                   
 myModule.ts |     100 |      100 |     100 |     100 |                   
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        10.967s
  1. use rewire package to mock funcA
    ...

Besides, you need to take a look at this docs: https://nodejs.org/api/modules.html#modules_exports_shortcut, to see what exactly does require do

@Ghost---Shadow
Copy link

Ghost---Shadow commented Apr 14, 2020

The solution in this stackoverflow post worked for me
https://stackoverflow.com/a/53402206/1217998

Basically first you convert all the functions you want to convert to jest.fn

jest.mock('../../utils', () => {
  const actualUtils = jest.requireActual('../../utils');
  const originalImplementation = actualUtils.someFun;

  return {
    ...actualUtils,
    someFun: jest.fn()
      .mockImplementation(originalImplementation),
  };
});
const utils = require('../../utils');

Then you can use it like normal if you want or mock it like this

jest.spyOn(utils, 'someFun').mockReturnValueOnce(true);

You can use this to get the original implementation

beforeEach(() => {
    jest.clearAllMocks();
  });

@mayorcoded
Copy link

The solution in this stackoverflow post worked for me
https://stackoverflow.com/a/53402206/1217998

Basically first you convert all the functions you want to convert to jest.fn

jest.mock('../../utils', () => {
  const actualUtils = jest.requireActual('../../utils');
  const originalImplementation = actualUtils.someFun;

  return {
    ...actualUtils,
    someFun: jest.fn()
      .mockImplementation(originalImplementation),
  };
});
const utils = require('../../utils');

Then you can use it like normal if you want or mock it like this

jest.spyOn(utils, 'someFun').mockReturnValueOnce(true);

You can use this to get the original implementation

beforeEach(() => {
    jest.clearAllMocks();
  });

Thank you!

@ephes
Copy link

ephes commented Jun 6, 2020

Jest has a very simple and clear example in their documentation of how to partially mock a module. This works with both ES import and Node require statements.
https://jestjs.io/docs/en/jest-object#jestrequireactualmodulename

Does not work when the mocked function is called from within the module.

@arturtakoev
Copy link

arturtakoev commented Jul 13, 2020

Also, I found that sometimes it can be useful to mock function the way that you don't change the original function but call function with some custom (additional) variables:

jest.mock('./someModule', () => {
  const moduleMock = require.requireActual('./someModule');
  return {
    ...moduleMock,
    // will mock this function 
    someFunction: (args) =>
      moduleMock.someFunction({
        ...args,
        customArgument,
      }),
  };
});

In my case I needed to pass come config to function without which it would use default one.

This is the only way I found to do this, so if you come up with some better or easier ideas, will be glad to hear :)

@magicmark
Copy link
Contributor

FWIW I've pulled together various approaches with runnable examples in https://github.com/magicmark/jest-how-do-i-mock-x/blob/master/src/function-in-same-module/README.md

@uxdxdev
Copy link

uxdxdev commented Sep 5, 2020

This does not answer the OPs question/issue, but is a solution with some refactoring involved. I've found separating my functions into different files, and then mocking those imports, is the easiest thing to do.

// package.json
...
"scripts": {
    "test": "jest",

...
"devDependencies": {
    "@babel/preset-env": "^7.11.5",
    "jest": "^24.9.0",
...
// babel.config.js

module.exports = {
    presets: [
        [
            '@babel/preset-env',
            {
                targets: {
                    node: 'current',
                },
            },
        ],
    ],
};
// module-utils.js

export const isBabaYaga = () => {
  return false
}
// module.js

import { isBabaYaga } from './module-utils'

export const isJohnWickBabaYaga = () => {
  return isBabaYaga()
}
// modules.test.js

import { isJohnWickBabaYaga } from './module';

jest.mock('./module-utils', () => ({
    isBabaYaga: jest.fn(() => true)
}))

test('John Wick is the Baba Yaga', () => {

    // when
    const isBabaYaga = isJohnWickBabaYaga()

    // then
    expect(isBabaYaga).toBeTruthy()
});
PASS  src/module.test.js
✓ John Wick is the Baba Yaga (4ms)

@RyanCCollins
Copy link

RyanCCollins commented Sep 17, 2020

I've recently run into this issue. None of the proposed solutions work for me because I'm unable to change the code. The babel-plugin-rewire also does not work for me. Is there any other solutions out there to test that a function was called by another function within the same module? This honestly seems like it should work or that there should be a babel plugin out there that does this. Any help would be much appreciated!

@magicmark
Copy link
Contributor

I've recently run into this issue. None of the proposed solutions work for me because I'm unable to change the code. The babel-plugin-rewire also does not work for me. Is there any other solutions out there to test that a function was called by another function within the same module? This honestly seems like it should work or that there should be a babel plugin out there that does this. Any help would be much appreciated!

Have you checked out #936 (comment)? There's a minimal repro in there that mocks functions calls in the same file.

@ezze
Copy link

ezze commented Dec 10, 2020

Reading this thread I was unable to find any working solution for TypeScript. This is how I've managed to solve the issue. 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.

@gerkenv
Copy link

gerkenv commented Dec 12, 2020

Reading this thread I was unable to find any working solution for TypeScript. This is how I've managed to solve the issue. 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 Option 1 from @nickofthyme comment worked in TS for me. Then you don't need any helpers.

@ezze
Copy link

ezze commented Dec 15, 2020

@ezze Option 1 from @nickofthyme comment worked in TS for me. Then you don't need any helpers.

@gerkenv Thanks for pointing it out but it requires to replace all function declarations by function expressions that sometimes is not a good option for already existing big projects.

@joebnb
Copy link

joebnb commented Feb 5, 2021

For anyone who comes across this looking for a solution, the following seems to be working for me when exporting many const/functions in one file, and importing them in a file which I'm testing

function mockFunctions() {
  const original = require.requireActual('../myModule');
  return {
    ...original, //Pass down all the exported objects
    test: jest.fn(() => {console.log('I didnt call the original')}),
    someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}),
  }
jest.mock('../myModule', () => mockFunctions());
const storage = require.requireMock('../myModule');
`

i'm come form 2021 🤣,nowadays this is more general in typescript and ES2015+.

i think jest Integrate this in a new api / override or other way would be better,waiting for ECMA improve this and update unify,not user implement it in every project.

this solution still buggy , if mock module have inner reference it's can't be mocked

@Xunnamius
Copy link

Xunnamius commented Apr 18, 2021

Five years late to the party, but if you're using Babel (i.e. @babel/parser) to handle transpiling your TS/ES6+/JS, as I am in this TypeScript project, I wrote babel-plugin-explicit-exports-references, a plugin that pretty elegantly solves this problem across my repos. It may solve it for you too.

I use it in my Jest tests along with spying to intuitively mock only specific module functions without rewriting my source, using TypeScript-incompatible dark magic like rewire, or tinkering with hacky nth-level-monkey-patched Jest mocks, beautiful as they are 🙂.

The plugin replaces all internal references to each module's own exported items with an explicit reference to those items in the form of a module.exports.X member expression. tl;dr it does what @cpojer described above, but automatically.

Before:

// file: myModule.ts
export function foo() {
  // expensive network calls
}

export function bar() {
  // ...
  foo();
}

export function baz() {
  // ...
  foo();
}

After babel-plugin-explicit-exports-references:

// file: myModule.ts
export function foo() {
  // expensive network calls
}

export function bar() {
  // ...
  module.exports.foo();
}

export function baz() {
  // ...
  module.exports.foo();
}

And finally, after @babel/preset-env and @babel/preset-typescript and everything else runs, the final result looks like:

// file: myModule.js
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.foo = foo;
exports.bar = bar;
exports.baz = baz;

function foo() {// expensive network calls
}

function bar() {
  // ...
  module.exports.foo();
}

function baz() {
  // ...
  module.exports.foo();
}

And that's it 🚀 For usage examples, see babel.config.js loading the plugin only when NODE_ENV=test, install-deps.ts exporting multiple functions that reference each other internally, and unit-utils.test.ts using Jest spies to test each function in isolation (without the plugin, the mock/spy at this line does not work).

I use the plugin only when running Jest tests (so: not in production), since it adds needless characters to the bundle.

@andrestone
Copy link

If you're coming to this thread to understand how to mock a function called by the function you're testing within the same module, this comment is exactly what you're looking for: #936 (comment)

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests