jest.mock factory doesn't work inside a test

Created on 12 Jan 2017  ·  38Comments  ·  Source: facebook/jest

Do you want to request a feature or report a bug?
Bug

What is the current behavior?

It seems the way to create a mock with a factory doesn't work inside test or it. It works only when the mock is defined at the root level of the file.

Here's my example of a mock:

jest.mock('services/feature', () => ({
    isEnabled: () => true
}));

What is the expected behavior?

Mocking a file inside a test should work.

Please provide your exact Jest configuration and mention your Jest, node, yarn/npm version and operating system.

Jest 18.0.0, Node 7.4, macOS

Confirmed Discussion

Most helpful comment

To change return value of a mock between tests, you can do something like this:

jest.mock('whatever');

// Get the mock function
const whatever = require('whatever');

test('test 1', () => {
  whatever.mockImplementation(() => 'hello');
});

test('test 2', () => {
  whatever.mockImplementation(() => 'world');
});

All 38 comments

jest.mock calls are automatically hoisted to the top of the file with babel-jest transform. You can omit this behaviour with jest.doMock. Have you tried that?

Same thing with doMock.

In the docs, I can read

Note: When using babel-jest, calls to mock will automatically be hoisted to the top of the code block. Use doMock if you want to explicitly avoid this behavior.

But... A test is a code block, right? So in my case, I don't expect to see any differences.

Here's a full test

it('renders with the enabled feature', () => {
  jest.mock('services/feature', () => ({
      isEnabled: () => true
  }));

  const component = renderer.create(
      <MyComponent />
  );

  const tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

Could you provide a repro of this in a GH repository?

Any advice @thymikee @cpojer for this issue?
I have several tests in the same file and I'd like to have different mock responses for each of them.

Glad I found this issue, I was breaking my head whyjest.mock() didn't work in my describe scope. Moved it to the top (below my imports in the test file) and it works.

For me it also applies to jest.mock() without a factory, using a __mocks__ folder containing the mocked file.

--edit

It's obviously necessary to hoist the jest.mock() statement to before the import statements. However @tleunen, this probably means it's not possible to mock the same file more then once, with different responses. Maybe you are best served by making the factory function more dynamic, so it can serve you different results in each test case.

In my mind it would make it more explicit if jest.mock() is always put outside the describe and it blocks. But this should then be clearly stated in the docs

So jest.mock is being hoisted to the function scope, that's why it won't work with requires (and definitely not imports which are hoisted to module scope) if you call it inside a function other than describe (which is treated specially by Jasmine).
Your jest.mock call will be hoisted to the top of that very function (not the module), that's why it won't work the way you expect.

Generally we advise to setup different mocks in beforeEach and afterEach if you want them different across test cases.

@cpojer could elaborate on this in detail, and if we want to hoist the calls to the upper scopes.

This is because you are requiring your modules when the module initializes (using import). jest.mock gets called way later. The way to solve this is:

beforeEach(() => { // or the specific test
  jest.mock('MyModule', () => …);
  const MyModule = require('MyModule');
  …
});

etc.

If you were to do this in beforeEach, I'm unclear how you'd differentiate tests (so how would you be giving a different mock for each test?)

Putting it inside the tests themselves works of course.

Can you then pass mocked module inside the tested component?

What if I am not importing/requiring the file I want to mock (e.g. a dependency of other file I am importing) but I want it scoped to a describe/it block? Or even if I want to mock differently for beforeEach/beforeAll test? Are those cases possible?

// A.js depends on B.js
import A from './A';

describe('myTest', () => {

    describe('myFirstScope', () => {
        beforeAll(() => {
            jest.mock('./B', () => ({
                myFirstMethod: jest.fn(),
            }));
        });

        // tests here
    });

    describe('mySecondScope', () => {
        beforeAll(() => {
            jest.mock('./B', () => ({
                mySecondMethod: jest.fn(),
            }));
        });

        // tests here
    });
});

In that case, you need to require A after mocking B. (Not using import, but require).

requiring A after mocking B

didn't work for me, was still seeing the original mock of B pass through to the result

@GoldAnna have you found a workaround for this issue?

@alayor I have not

As many times as I've run into this problem, I'm now convinced I'm either not testing properly, not using jest as the authors intended, or some combination of both. Further proof is that almost none of the mock related examples in jest's documentation seem like real world examples to me...they probably are and my approach is probably incorrect.

That said, the following works for me, and all of the following tests pass. Note that to get back to the original version of ModuleB, I have to call both jest.resetModules() and jest.unmock('./moduleB')...the order of those doesn't matter.

// Module A
const ModuleB = require('./moduleB');
const ModuleA = function() {
  this.title = new ModuleB().title;
  return this;
};
module.exports = ModuleA;

// Module B
const ModuleB = function() {
  this.title = 'Module B - Original'
  return this;
};
module.exports = ModuleB;

// Tests
describe('Jest a few tests', () => {
  it('should do something', () => {
    jest.resetModules();
    jest.mock('./moduleB', () => function() {
      this.title = 'Module B - Mock 1'
      return this;
    });
    const ModuleA = require('./moduleA');
    const moduleA = new ModuleA();
    expect(moduleA.title).toEqual('Module B - Mock 1');
  });

  it('should do something else', () => {
    jest.resetModules();
    jest.mock('./moduleB', () => function() {
      this.title = 'Module B - Mock 2'
      return this;
    });
    const ModuleA = require('./moduleA');
    const moduleA = new ModuleA();
    expect(moduleA.title).toEqual('Module B - Mock 2');
  });

  it('should do something original', () => {
    jest.resetModules();
    jest.unmock('./moduleB');
    const ModuleA = require('./moduleA');
    const moduleA = new ModuleA();
    expect(moduleA.title).toEqual('Module B - Original');
  });
});

Hey,

Nothing here works for me.
Can someone please explain why it is not possible to use jest.mock inside it?

Thank you

I think this is still an issue that should maybe be reopened as a feature request. I want to write tests in isolation. Dumping things in a mocks folder or rewiring with a beforeEach usually ends up with a junk drawer of mocks/weird data instantiation that gets lugged around into every test. I want to write a small mock and make a small test pass.

We have no real way of isolating individual tests (considering test.concurrent). If we adopted an API similar to tap or ava it'd be possible, but I don't think it's possible with the constraints imposed by the current global API. Super happy to be proven wrong!

Had a struggle with multiple methods here to refactor a test previously written in mocha and proxyquire, end up with separating the test into different files for different mock.

@gcox Have you tried mocking (and requiring moduleA after) at the top-level of the module and then changing the mock's implementation for each test that needs different behavior? You can use mockImplementation to do so. This is how we've solved this in our test suites.

@antgonzales I think that's difficult to do if module imports are hoisted to the top of a module. Modifying a previously-imported module's references seems non-trivial to impossible in node.

@jkomusin Definitely. That's just not what the OP was asking for, IMO. I believe they were asking, specifically, "How do I force require('whatever') to return a different mock across multiple adjacent tests rather than the same object?".

To change return value of a mock between tests, you can do something like this:

jest.mock('whatever');

// Get the mock function
const whatever = require('whatever');

test('test 1', () => {
  whatever.mockImplementation(() => 'hello');
});

test('test 2', () => {
  whatever.mockImplementation(() => 'world');
});

@gcox answer did work for me, but @SimenB also did work, and is so much simpler!

@rafaeleyng but @SimenB doesn't work if what you export from the module is not a function...

@gcox example contains most of the information which I have been searching for in the docs for the past few hours. I suggest that it should be included in the official documentation which is very sparse at the moment anyway.

@schumannd is right. Please make the official documentation more clear and crisp.

PRs always welcome improving the docs 🙂

Module example based on
https://github.com/facebook/jest/issues/2582#issuecomment-378677440 ❤️

Heya, found this via the linked issue and I figured it out:

jest.mock('child_process')
const childProcess = require('child_process')

describe('foo', () => {
  test('bar', () => {
    childProcess.execSync.mockImplentation(jest.fn().mockReturnValueOnce('wibble'))
    // code that itself requires child_process
    expect(childProcess.execSync.mock.results[0]).toEqual('wibble')
  })

  test('baz', () => {
    childProcess.execSync.mockImplentation(jest.fn().mockReturnValueOnce('wobble'))
    // code that itself requires child_process
    expect(childProcess.execSync.mock.results[0]).toEqual('wobble')
  })
})

If you need to mock multiple functions/methods on the required code, you need to set them up separately instead of using the factory when you do it globally.

If you like me just need a single function, you can simplify the whole thing:

jest.mock('child_process')
const { execSync } = require('child_process')

describe('foo', () => {
  test('bar', () => {
    execSync.mockImplentation(jest.fn().mockReturnValueOnce('wibble'))
    // code that itself requires child_process
    expect(execSync.mock.results[0]).toEqual('wibble')
  })

  test('baz', () => {
    execSync.mockImplentation(jest.fn().mockReturnValueOnce('wobble'))
    // code that itself requires child_process
    expect(execSync.mock.results[0]).toEqual('wobble')
  })
})

what about mocking non function dependencies like json files, or mapped constants. Please make the documentation more clear on how to handle this...

@gcox Your solution is the only one I've found for when an imported module under test imports another module that I had a manual mock for in a __mocks__ folder.

For example, the test file calls jest.mock('./ModuleA'), which has a mock in __mocks__/ModuleA.js. But ModuleA is not under test, ModuleB - which requires ModuleA - is what's under test. Without your solution, ModuleB would get the actual implementation of ModuleA, not the mock.

This seems like really odd behaviour from jest. I would expect that when I call jest.mock on a module, any module under test that has a dependency on the mocked module would use the mock. Does this seem bizarre that it doesn't work that way, or am I going about this completely incorrectly?

@SimenB In your example, you're requiring the mocked module, and executing it in the test, which does work. Would you expect the scenario I described above to work as well? Thanks so much.

@SimenB worked your simpler solution for me. Thanks! Wish could found it earlier before I spent time with many different ways

In my case I was mocking and importing the wrong path to the module I wanted to stub. I had different casing in the file path so my editor thought it was okay but just was failing.

Was

const { stubMethod } = require("./path/to/File");
jest.mock("./path/to/File");

Changed to (change casing of File to file):

const { stubMethod } = require("./path/to/file");
jest.mock("./path/to/file");

Hope this helps someone else.

Jasmine spyOn() has no such problem _inside_ tests. Jest supposedly uses jasmine by default? Why doesn't it work inside test?

Jasmine Example:

spyOn(require('moduleB'), 'functionA').and.callFake(() => true);

What I don't quite understand when it comes to mocking modules is that it is always described as if you want to use the module primarily in your test-functions. E.g. if you look on the example code for the doMock function in the docs:

test('moduleName 2', () => {
  jest.doMock('../moduleName', () => {
    return {
      __esModule: true,
      default: 'default2',
      foo: 'foo2',
    };
  });
  return import('../moduleName').then(moduleName => {
    expect(moduleName.default).toEqual('default2');
    expect(moduleName.foo).toEqual('foo2');
  });
});

In this example, you have access to the newly mocked version of "moduleName" inside the test function only. For me it is way more important that my "non-test"-code would have access to the mocked version of the module as well. E.g. a Class that I am using inside my test, that has imported "moduleName" would still be using the original version, not the mocked one.

Seems to be related: https://github.com/facebook/jest/issues/3236

Tip - if you have a module that exports a primitive value, e.g.:

```auth.js
export const isUserAdmin = getSetting('admin');

And you want to use a mock value instead, in the test, then a simple require and assignment seems to do the trick:

```deleteUser.js
const auth = require('../auth');

describe('deleteUser', () => {
  it('can delete if admin', () => {
    auth.isUserAdmin = true;

    // call method that depends on isUserAdmin value
    // and assert on the result
  });
});

So basically, if you want to mock an object that is used indirectly by the code you test the jest.doMock() function won't work:

import myModuleToTest from './myModuleTotest'

describe('Given my module', () => {
  it('property1 will work as expect', () => {
    // Testing parts of the module that don't need to be mocked
  })

  it('property2 will work as expected', () => {
    jest.doMock('./myOtherModule', () => {
      return {
        __esModule: true,
        default: 'default2',
        foo: 'foo2',
      };
    });

    import('./myOtherModule').then(myOtherModule => {
      // I'm not interested on the mocked module myOtherModule but on the module that makes use of it
      myModuleToTest.doSomethingToSomeProperty(); // At this point myOtherModule's original module and not its mocked version will be used by myModuleToTest
      expect(myModuleToTest.someProperty).toBe('thisWillFail'); // The test won't pass because the mocked version wasn't used
    });
  });
});

As of Jest 26 there is no way to mock more than once a module exporting an Object that is used indirectly (I mean mocking something else than a Function since there is no mockFn.mockImplementation(fn) for mocked Objects). Is this correct or I am missing something? The only solution then is having more than one test file to test the same module.

You have to import myModuleToTest after your mock because if it imports
before the mock does not make sense. So, don't use import .... on top or
inside the callback because it's hoisted anyway.

On Tue, Jul 7, 2020, 10:24 PM Antonio Redondo notifications@github.com
wrote:

So basically, if you want to mock an object that is used indirectly by the
code you test the jest.doMock() function won't work:

import myModuleToTest from './myModuleTotest'
it('will work', () => {
jest.doMock('./myOtherModule', () => {
return {
__esModule: true,
default: 'default2',
foo: 'foo2',
};
});

return import('../myOtherModule').then(myOtherModule => {
// I'm not interested on the mocked module myOtherModule but on the module that makes use of it
myModuleToTest.doSomethingToSomeProperty(); // At this point myOtherModule's original module and not its mocked version will be used by myModuleToTest
expect(myModuleToTest.someProperty).toBe('thisWillFail'); // The test won't pass because the mocked version wasn't used
});});

As of Jest 26 there is no way to mock a module exporting an Object (I
mean something else than a Function) that is used indirectly. Is this
correct or I am missing something? The only solution then is having more
than one test file to test the same module.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/facebook/jest/issues/2582#issuecomment-655110424, or
unsubscribe
https://github.com/notifications/unsubscribe-auth/AAJR3UW6HARW44ZLKAUB7PLR2N77NANCNFSM4C4I7QSQ
.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

samzhang111 picture samzhang111  ·  3Comments

stephenlautier picture stephenlautier  ·  3Comments

hramos picture hramos  ·  3Comments

withinboredom picture withinboredom  ·  3Comments

Antho2407 picture Antho2407  ·  3Comments