jest: jest.mock factory doesn't work inside a test

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

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 100
  • Comments: 54 (4 by maintainers)

Most upvoted comments

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

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.

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.

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

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

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

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

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.

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.

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

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

Hey,

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

Thank you

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

requiring A after mocking B

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

If you want to mock an object that is used indirectly by the code tested 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 (mocking something else than a Function since there is no mockFn.mockImplementation(fn) for Objects).

The only solution then is having more than one test file to test the same module.

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

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?

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

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:

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

Another solution could be using an external variable that is changed inside each test.

let locale: string

jest.mock('@/hooks/useTranslation', () => ({
  useTranslation: () => {
    return {
      locale,
    }
  },
}))

describe('Test suite', () => {
  it('test english language', () => {
    locale = 'en'
    const component = render(<Component />)
    ...
  })

  it('test italian language', () => {
    locale = 'it'
    const component = render(<Component />)
    ...
  })
})

None of the solutions mentioned seemed to work for me, so I added this above my describe

jest.mock('date-fns-tz', () => ({
    getTimezoneOffset: jest.fn().mockImplementation((timezone, date) => {
        return timezone
    })
}));

Then I updated the mocked data that was being passed into the getTimezoneOffset function in each test and that worked.

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.

@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.

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

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 what worked for me to mock a custom hook being used inside code for specific test suite.

import * as useCustomHook from './useCustom'

describe('My test suite', () => {
  const mockSaveMethod = jest.fn();
  const mockDeleteMethod = jest.fn();

  const clickOnElementByTestId = (testId: string) => {
    fireEvent.click(screen.getByTestId(testId));
  };

  beforeEach(() => {
    jest.spyOn(useCustomHook, 'useCustom').mockImplementation(() => {
      return {
        saveMethod: mockSaveMethod,
        deleteMethod: mockDeleteMethod
      };
    });
  });

  test('should call save on clicking save button', async () => {
    render('<MyComponent />');
    clickOnElementByTestId('testIds.saveButton');
   await waitFor(() => {
      expect(mockSaveMethod).toHaveBeenCalledTimes(1);
    });
  });

  test('should call publish on clicking publish button', async () => {
    render('<MyComponent />');
    clickOnElementByTestId('testIds.publishButton');
     await waitFor(() => {
        expect(mockDeleteMethod).toHaveBeenCalledTimes(1);
      });
  });
});

and useCustom hook would be something like this

export function useCustom(){
   const saveMethod=()=>{};
   const deleteMethod=()=>{};

  return {
    saveMethod,
    deleteMethod
  }
}

and the component under testing MyComponent will be something like this

export function MyComponent(){
   const { saveMethod, deleteMethod } = useCustom();
   
   ...
   ...
}

Just encountered this really tedious bit of error (react), but I think I figured it out. The solution was staring at me all this time. this bit of info in the documentation is the key:

Warning: Importing a module in a setup file (as specified by setupFilesAfterEnv) will prevent mocking for the module in question, as well as all the modules that it imports.

This means that if you happen to have imported a module that also uses/imports that module (that you are about to mock) in the setup file, you will not be able to mock that module! In my case, I was refactoring my code so that everything that relates to testing is in one export (index) file (including the methods I was about to mock! It’s a Graphql + MSW setup).

@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.

@baspinarenes 's solution worked for me! Thanks!

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

worked like magic very good @SimenB

I will share my example for those in the same situation. I tried with the following codes:

// utils.ts
import axios from "axios";

export async function get(apiUrl: string): Promise<any> {
  try {
    const response = await axios.get(apiUrl);

    return response.data;
  } catch (error) {
    return null;
  }
}
// utils.test.ts
import { get } from "./utils";

describe("utils tests", () => {
  describe("get() tests", () => {
    test("should return product when request is success", async () => {
      jest.mock("axios");
      const axios = require("axios");
      const mockedAxios = jest.mocked(axios);

      const apiUrl = "https://dummyjson.com/product/1";
      const mockProduct = {
        id: 1,
        title: "iPhone 9",
        description: "An apple mobile which is nothing like apple",
        price: 549,
        discountPercentage: 12.96,
        rating: 4.69,
        stock: 94,
        brand: "Apple",
        category: "smartphones",
      };

      mockedAxios.get.mockResolvedValue({
        data: mockProduct,
      });

      const result = await get(apiUrl);

      expect(mockedAxios.get).toHaveBeenCalledWith(apiUrl);
      expect(result).toStrictEqual(mockProduct);
    });
  });
});

And I couldn’t mock it. I realized later. I needed to mock axios before importing the file I’m using it. So:

// utils.test.ts
describe("utils tests", () => {
  describe("get() tests", () => {
    test("should return product when request is success", async () => {
      jest.mock("axios");
      const axios = require("axios");
      const mockedAxios = jest.mocked(axios);
      const { get } = require("./utils");

      const apiUrl = "https://dummyjson.com/product/1";
      const mockProduct = {
        id: 1,
        title: "iPhone 9",
        description: "An apple mobile which is nothing like apple",
        price: 549,
        discountPercentage: 12.96,
        rating: 4.69,
        stock: 94,
        brand: "Apple",
        category: "smartphones",
      };

      mockedAxios.get.mockResolvedValue({
        data: mockProduct,
      });

      const result = await get(apiUrl);

      expect(mockedAxios.get).toHaveBeenCalledWith(apiUrl);
      expect(result).toStrictEqual(mockProduct);
    });
  });
});

I can move it to beforeEach:

// utils.test.ts
describe("utils tests", () => {
  describe("get() tests", () => {
    let mockedAxios: jest.Mocked<any>;

    beforeEach(() => {
      jest.mock("axios");
      const axios = require("axios");
      mockedAxios = jest.mocked(axios);
    });

    test("should return product when request is success", async () => {
      const { get } = require("./utils");

      const apiUrl = "https://dummyjson.com/product/1";
      const mockProduct = {
        id: 1,
        title: "iPhone 9",
        description: "An apple mobile which is nothing like apple",
        price: 549,
        discountPercentage: 12.96,
        rating: 4.69,
        stock: 94,
        brand: "Apple",
        category: "smartphones",
      };

      mockedAxios.get.mockResolvedValue({
        data: mockProduct,
      });

      const result = await get(apiUrl);

      expect(mockedAxios.get).toHaveBeenCalledWith(apiUrl);
      expect(result).toStrictEqual(mockProduct);
    });
  });
});

And I can override the global mock (simplified):

import { get } from "./utils";

jest.mock("axios", () => {
  return {
    get: jest
      .fn()
      .mockRejectedValueOnce(new Error("Error occured when fetching data!")),
  };
});

describe("utils tests", () => {
  beforeEach(() => {
    jest.resetModules();
  });

  describe("get() tests", () => {
    test("should return product when request is success", async () => {
      const apiUrl = "https://dummyjson.com/product/1";
      const mockProduct = {
        id: 1,
        title: "iPhone 9",
        description: "An apple mobile which is nothing like apple",
        price: 549,
        discountPercentage: 12.96,
        rating: 4.69,
        stock: 94,
        brand: "Apple",
        category: "smartphones",
      };

      jest.doMock("axios", () => ({
        get: jest.fn().mockResolvedValueOnce({
          data: mockProduct,
        }),
      }));
      const { get } = require("./utils");

      const result = await get(apiUrl);

      expect(result).toStrictEqual(mockProduct);
    });

    test("should return null when request is failed", async () => {
      const apiUrl = "https://dummyjson.com/product/1000";

      const result = await get(apiUrl);

      expect(result).toBeNull();
    });
  });
});

mock and doMock difference is only about hoisting. I hope it solves some people’s problem.

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 .

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.

Sure. Here it is: https://github.com/tleunen/jest-issue-2582

Both tests render “Disabled” even though one of then has a mock to renders “Enabled” instead. See these: https://github.com/tleunen/jest-issue-2582/blob/master/src/MyComponent.js https://github.com/tleunen/jest-issue-2582/blob/master/src/__tests__/MyComponent.spec.js

Thanks.