jestjs / jest

Delightful JavaScript Testing.
https://jestjs.io
MIT License
44.21k stars 6.46k forks source link

How to mock specific module function? #936

Closed seibelj closed 8 years ago

seibelj commented 8 years ago

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.

maletor commented 4 years ago

@nickofthyme, Option 1 is correct as you have it. But if you change the code to:

const foo = () => {}
export { foo }

Then it breaks. Presumably because you create a new object literal and export that.

nickofthyme commented 4 years ago

Interesting observation. Thanks @maletor

johncmunson commented 4 years ago

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

nickofthyme commented 4 years ago

@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 ```ts 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 commented 4 years ago

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 commented 4 years ago

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. :bowing_man:

bitttttten commented 4 years ago

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 commented 4 years ago

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,
```js
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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 https://github.com/facebook/jest/issues/936#issuecomment-659597840? There's a minimal repro in there that mocks functions calls in the same file.

ezze commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

@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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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: https://github.com/facebook/jest/issues/936#issuecomment-545080082

github-actions[bot] commented 3 years ago

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.