Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between resetAllMocks, resetModules, resetModuleRegistry, restoreAllMocks in Jest

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.

like image 520
tmp dev Avatar asked Sep 28 '19 23:09

tmp dev


People also ask

What is clearAllMocks?

clearAllMocks() and clearMocks:[boolean] Resets all the mocks usage data, not their implementation. In other words, it only replaces fn. mock.

How do you mock a function inside another function in Jest?

You can create a namespace that you export as the default object and call b using the namespace. This way, when you call jest. mock it will replace the b function on the namespace object. const f = require('./f'); jest.

What is mocking in Jest?

Mock functions allow you to test the links between code by erasing the actual implementation of a function, capturing calls to the function (and the parameters passed in those calls), capturing instances of constructor functions when instantiated with new , and allowing test-time configuration of return values.

How do you use spyOn function in Jest?

To spy on an exported function in jest, you need to import all named exports and provide that object to the jest. spyOn function. That would look like this: import * as moduleApi from '@module/api'; // Somewhere in your test case or test suite jest.

What does jest module reset actually do?

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.

What does “restoremocks” mean in jest?

Since “restoreMocks”: true automatically restores a spy prior to executing each unit test spec (and prior to any custom beforeEach (..) ), it's best to only use jest.spyOn ( ..) inside either: a beforeEach ( ..) Whereas the following usage of jest.spyOn ( ..) will give issues:

What happens when you mock a jest module?

Modules that are mocked with jest.mock are mocked only for the file that calls jest.mock. Another file that imports the module will get the original implementation even if it runs after the test file that mocks the module. Returns the jest object for chaining.

What does resetallmocks() do?

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. A superset of clearAllMocks () which also takes care of resetting the implementation to a no return function.


2 Answers

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')
      })
    })
  })
})
like image 75
sepehr Avatar answered Oct 06 '22 23:10

sepehr


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);
    });
});
like image 45
鄭元傑 Avatar answered Oct 06 '22 22:10

鄭元傑