Reputation: 9193
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.
Upvotes: 168
Views: 107867
Reputation: 18475
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
This table shows the summary; the left column is the type of test you're doing:
would reset this test | clearAllMocks() |
resetAllMocks() |
restoreAllMocks() |
resetModules() |
---|---|---|---|---|
expect(mock).toHaveBeenCalled... |
✓ | ✓ | ✓ | |
mock.mockImplementation(...) |
✓ | ✓ | ||
jest.spyOn(o,'f') |
✓ | |||
require('module').global++ |
✓ |
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')
})
})
})
})
Upvotes: 293
Reputation: 1657
Thanks for @sepehr answer.
I think it would be easier to understand by example.
Quick tips:
clear
before you usereset
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);
});
});
Upvotes: 64