jest.clearAllMocks vs jest.resetAllMocks vs jest.restoreAllMocks explained

edwinwong90

Edwin Wong

Posted on August 31, 2022

jest.clearAllMocks vs jest.resetAllMocks vs jest.restoreAllMocks explained

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)
})
Enter fullscreen mode Exit fullscreen mode

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: []
}
Enter fullscreen mode Exit fullscreen mode

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.
})
Enter fullscreen mode Exit fullscreen mode

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()
})
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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()
})
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

I hope this will help you have better understanding. Like or Unicorn or Save if it does help! Thank you.

💖 💪 🙅 🚩
edwinwong90
Edwin Wong

Posted on August 31, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related