I know is been hard to remember and confuse about what these clearAllMocks, resetAllMocks and restoreAllMocks actually does and why we need that?
Allow me to clear your mind!
jest.clearAllMocks
Clear all mock usage data such as mock.calls
, mock.instances
, mock.contexts
and mock.results
but not their implementation.
Every time the mock function get called it will keep those usage contexts into the mock function object itself.
Let's take a following example. We going to test the getRandomNumber
that actually calling randomNumberGenerator()
to get the random number. So, In this case we will mock the random.service
module.
// main.test.js
jest.mock('random.service')
const { randomNumberGenerator } = require('random.service')
const getRandomNumber => randomNumberGenerator()
it('should return a number', () => {
randomNumberGenerator.mockReturnValue(7)
const num = getRandomNumber()
console.log(randomNumberGenerator.mock) // ๐ check this out!
expect(num).toBe(7)
})
Once you call getRandomNumber
function, the mock randomNumberGenerator
will also get called accordingly. After this, you can console.log(randomNumberGenerator.mock)
and you will see something like this.
{
calls: [ [] ],
contexts: [
<ref *1> {...}
],
instances: [
<ref *1> {...}
],
invocationCallOrder: [ 1 ],
results: [ { type: 'return', value: 2 } ],
lastCall: []
}
The contexts will be useful for some assertions purpose like to expect number of calls, do function have return and more. This also could be useful for certain use case that may need these context for cross test case assertions.
However, this also could lead to some problem if you don't clear it. Example below
it('example 1', () => {
randomNumberGenerator.mockReturnValue(7)
const num = getRandomNumber()
expect(randomNumberGenerator).toBeCalledTimes(1)
})
it('example 2', () => {
randomNumberGenerator.mockReturnValue(8)
const num = getRandomNumber()
expect(randomNumberGenerator).toBeCalledTimes(1) // This will failed!! because it expect 2 times.
})
The second example will failed and this is because the mock.calls
has 2 counts. To fix it, you need to use jest.clearAllMocks
in jest global like afterEach
or beforeEach
beforeEach(() => {
jest.clearAllMocks()
})
This tell jest to clear all the mock usage data before the next test case start.
jest.resetAllMocks
A superset of clearAllMocks()
and it also reset the mock function implementations with brand new jest.fn()
.
By default, all mock function without implementation it will always return undefined
. And once added implementation it will keep the implementation in the mock function object.
Following code will explain everything.
it('example 1', () => {
randomNumberGenerator.mockReturnValue(7)
const num = getRandomNumber()
expect(num).toBe(7)
})
it('example 2', () => {
// we didn't mock `randomNumberGenerator` in this test but this will return 7 because the last test case is added implementation.
console.log(randomNumberGenerator())
const num = getRandomNumber()
expect(num).toBe(7)
})
This is useful when you need re-usable the mock function. So you don't have to write again in every test case.
If you want every test case is fresh, new and standalone. You should use it before each test case start. It can ensure side effect free for each test case and worry less.
jest.restoreAllMocks
Restore all mock back to their original implementation and it only works for mock was created with jest.spyOn
.
For some reason, you need to mock the module partially with certain function. A classic example will be testing the date time related as following example.
it('should be my birthday', () => {
jest.spyOn(Date, 'now').mockReturnValue('2022-08-31T15:23:19.576Z')
const result = isTodayMyBirthday()
expect(result).toBe(true)
})
This works well but it is dangerous because spyOn is kind of like mutate the module object and if you forgot to restore all mocks, the subsequent test is using the mock version of Date.now()
and this is not really obvious and hard to debug.
Like previous solution did, just clear it before every next test case.
beforeEach(() => {
jest.restoreAllMocks()
})
Be cautions when you see spyOn in your test case it could be the reason to make your test failed.
Conclusion
As you can see, the mock API behavior is always keep the state and you have to clear/reset/restore it explicitly. There is a better to do this if you want your test case always start with a new fresh and side effect free. You can do it in jest config.
// jest.config.js
const config = {
clearMocks: true,
resetMocks: true,
restoreMocks: true
}
I hope this will help you have better understanding. Like or Unicorn or Save if it does help! Thank you.
Top comments (2)
good explanation.
I just have one concern, since resetMocks is the same as clearMocks but also resets the mock implementations, why would you have clearMocks: true and resetMocks: true in the jest config? because resetMocks: true will do the same as clearMocks plus resetting implementation?
thanks
Yes @joseluis11 good point! I just show all the configs which you can configure. You can just resetMocks: true will do.