Difference between resetAllMocks, resetModules, resetModuleRegistry, restoreAllMocks in Jest

JavascriptUnit TestingMockingJestjs

Javascript Problem Overview


I'm trying to wrap my head around the following in Jest:

resetAllMocks, resetModules, resetModuleRegistry and restoreAllMocks

and I'm finding it difficult.

I read the jest documentation but it's not too clear. I would appreciate it if someone can please provide me with an example of how the above work and they are different from each other.

Javascript Solutions


Solution 1 - Javascript

The following sections explain the behaviors of each function and its corresponding config directive. In the case of config directives, the explained behavior takes place in between each test making them more and more isolated from the other tests.

References to fn are implying a sample jest mock function under each of these actions.

jest.clearAllMocks() and clearMocks:[boolean]

Resets all the mocks usage data, not their implementation. In other words, it only replaces fn.mock.calls and fn.mock.instances properties of a jest mock function.

jest.resetAllMocks() and the resetMocks:[boolean]

A superset of clearAllMocks() which also takes care of resetting the implementation to a no return function. In other words, it will replace the mock function with a new jest.fn(), not just its fn.mock.calls and fn.mock.instances.

jest.restoreAllMocks() and restoreMocks:[boolean]

Similar to resetAllMocks(), with one very important difference. It restores the original implementation of "spies". So, it goes like "replace mocks with jest.fn(), but replace spies with their original implementation".

So, in cases where we manually assign things with jest.fn() (not spies), we have to take care of implementation restoration ourselves as jest won't be doing it.

jest.resetModules() and resetModules:[boolean]

It resets Jest's module registry which is a cache for all required/imported modules. Jest will re-import any required module after a call to this. Imagine a clean slate without having to deal with all the mocked out modules in other tests.

jest.resetModuleRegistry

It's just an alias for resetModules, see:
https://github.com/facebook/jest/blob/7f69176c/packages/jest-runtime/src/index.ts#L1147


See how clearing, resetting and restoring differ in action:
https://repl.it/@sepehr/jest-mock-api-reset-restore#jest-mock-apis.test.js

PASS  ./jest-mock-apis.test.js

jest mock reset/restore api
  when calling mockReset() on a test double with custom impl.
    if the test double is a spy
      ✓ jest replaces the impl. to a new undefined-returning jest.fn() (18ms)
    if the test double is "not" a spy
      ✓ jest replaces the impl. to a new undefined-returning jest.fn() (17ms)

  when calling mockRestore() on a test double with custom impl.
    if the test double is "not" a spy
      ✓ jest resets the impl. to a new undefined-returning jest.fn() (2ms)
    if the test double is a spy
      ✓ jest restores the original impl. of that spy (7ms)

describe('jest mock reset/restore api', () => {
  
  describe('when calling mockReset() on a test double with custom impl.', () => {
    describe('if the test double is a spy', () => {
      test('jest replaces the impl. to a new undefined-returning jest.fn()', () => {
        const module = { api: () => 'actual' }
        jest.spyOn(module, 'api').mockImplementation(() => 'spy mocked')
        
        expect(module.api()).toStrictEqual('spy mocked')
        expect(module.api).toHaveBeenCalledTimes(1)

        module.api.mockReset()

        expect(module.api()).toStrictEqual(undefined)
        expect(module.api).toHaveBeenCalledTimes(1)
      })
    })
    

    describe('if the test double is "not" a spy', () => {
      test('jest replaces the impl. to a new undefined-returning jest.fn()', () => {
        const api = jest.fn(() => 'non-spy mocked')
        
        expect(api()).toStrictEqual('non-spy mocked')
        expect(api).toHaveBeenCalledTimes(1)

        api.mockReset()

        expect(api()).toStrictEqual(undefined)
        expect(api).toHaveBeenCalledTimes(1)
      })
    })
  })

  describe('when calling mockRestore() on a test double with custom impl.', () => {
    describe('if the test double is "not" a spy', () => {
      test('jest resets the impl. to a new undefined-returning jest.fn()', () => {
        const api = jest.fn(() => 'non-spy mocked')
        
        expect(api()).toStrictEqual('non-spy mocked')
        expect(api).toHaveBeenCalledTimes(1)

        api.mockRestore()

        expect(api()).toStrictEqual(undefined)
        expect(api).toHaveBeenCalledTimes(1)
      })
    })

    describe('if the test double is a spy', () => {
      test('jest restores the original impl. of that spy', () => {
        const module = { api: () => 'actual' }
        jest.spyOn(module, 'api').mockImplementation(() => 'spy mocked')
        
        expect(module.api()).toStrictEqual('spy mocked')
        expect(module.api).toHaveBeenCalledTimes(1)

        module.api.mockRestore()

        expect(module.api()).toStrictEqual('actual')
        expect(module.api).not.toHaveProperty('mock')
      })
    })
  })
})

Solution 2 - Javascript

Thanks for @sepehr answer.
I think it would be easier to understand by example.

Quick tips:

  1. If you want to test mock function called times, clear before you use
  2. If you want to make sure mock return value wouldn't pollute other test case, call reset
  3. If you want to use origin method instead of mock implementation, call restore.
import {Calculator} from './calculator';

describe('calculator add', function () {
    let calculator = new Calculator();
    const mockAdd = jest.spyOn(calculator, 'add');
    it('mock the add method', function () {
        calculator.add = mockAdd.mockReturnValue(5);
        expect(calculator.add(1, 2)).toBe(5);
    });

    it('because we didnt clear mock, the call times is 2', function () {
        expect(calculator.add(1, 2)).toBe(5);
        expect(mockAdd).toHaveBeenCalledTimes(2);
    });

    it('After clear, now call times should be 1', function () {
        jest.clearAllMocks();
        expect(calculator.add(1, 2)).toBe(5);
        expect(mockAdd).toHaveBeenCalledTimes(1);
    });

    it('we reset mock, it means the mock has no return. The value would be undefined', function () {
        jest.resetAllMocks();
        expect(calculator.add(1, 2)).toBe(undefined);
    });

    it('we restore the mock to original method, so it should work as normal add.', function () {
        jest.restoreAllMocks();
        expect(calculator.add(1, 2)).toBe(3);
    });
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiontmp devView Question on Stackoverflow
Solution 1 - JavascriptsepehrView Answer on Stackoverflow
Solution 2 - Javascript鄭元傑View Answer on Stackoverflow