lmk123 / blog

个人技术博客,博文写在 Issues 里。
https://github.com/lmk123/blog/issues
623 stars 35 forks source link

jest 中的 `resetAllMocks()` 和 `restoreAllMocks()` #114

Open lmk123 opened 1 year ago

lmk123 commented 1 year ago

此文章使用 jest v29.3.1 版本。jest v29.7.0 测试结果一样。

在写测试的时候,我们一般都需要在运行每个测试之前清除 mock 函数的状态,避免测试代码之间 mock 函数的状态互相影响。jest 提供了两个函数来清除 mock 函数的状态:

如果 mock 函数比较多的话,挨个调用这俩方法比较繁琐,所以 jest 还提供了两个方法:

但在写测试的过程中,我时常出现预期之外的结果,这让我对这四个方法的作用范围产生了怀疑,因此,我用代码测试了 jest 的这两个方法,最终代码如下:

https://github.com/lmk123/test-jest/blob/master/mocks.test.js

测试结论:

  1. mockFn.mockReset()jest.resetAllMocks() 的表现是一致的:
    • 它们都会清除 jest.fn()jest.spyOn() 产生的 mock 函数的调用数据与函数定义。
    • 它们都不会将对象上被 spyOn 的方法还原为原函数。
  2. mockFn.mockRestore() 会将对象上被 spyOn 的方法还原为原函数,同时也会清除调用数据。
  3. jest.restoreAllMocks() 会将对象上被 spyOn 的方法还原为原函数,但是它不会清除 jest.fn()jest.spyOn() 产生的 mock 函数的调用数据与函数定义。

这下我总算明白不符合我预期的地方了,也就是第 3 点。文档上说:

jest.restoreAllMocks()... Equivalent to calling .mockRestore() on every mocked function. Beware that jest.restoreAllMocks() only works when the mock was created with jest.spyOn.

但事实上,它并没有 “calling .mockRestore() on every mocked function”,因为它只会将对象上被 spyOn 的方法还原为原函数,但却没有清除 mock 函数的调用数据与函数定义,而 .mockRestore() 是会清除的。文档上的 “Beware that jest.restoreAllMocks() only works when the mock was created with jest.spyOn” 让我以为它只会对 spyOn 产生的 mock 函数调用 mockRestore(),但实际上不是这样的。

一句话总结 jest.restoreAllMocks()mockFn.mockRestore() 的关系:前者会且只会将对象上被 spyOn 的方法还原为原函数,而 mockRestore() 还会清除调用数据与函数定义。

这再一次验证了我一直推崇的“文档是代码的一部分”这一观点,一句不清不楚甚至可以说是错误的文档真的会浪费使用者不少时间。

所以,如果想要完全还原所有 mock 函数,需要同时调用 jest.resetAllMocks()jest.restoreAllMocks(),前者清除所有 mock 函数的调用数据与函数定义,后者则将所有对象上被 spyOn 的方法还原为原函数。

jest 提供了两个配置来自动调用这俩方法:resetMocksrestoreMocks,如果你将这俩都设为了 true,则相当于在测试文件最上方插入了下面这段代码:

beforeEach(() => {
  jest.resetAllMocks()
  jest.restoreAllMocks()
})

且经过测试,这个 beforeEach 是第一个执行的。

题外话:关于“清除函数定义”与”将对象上被 spyOn 的方法还原为原函数”

刚接触 jest 时,我对“还原 mock 函数”这一概念感到混淆,在这里我详细解释一下。

无论是用 jest.fn() 还是 jest.spyOn() 产生的 mock 函数,调用 mockReset()mockRestore() 都会将它们的函数定义变成“返回 undefined”的状态,也就是我前文所说的“清除函数定义”。

代码测试如下:

const normalMockFn = jest.fn(() => '函数定义')
const obj = { method: () => '原函数' }
const spyOnMockFn = jest.spyOn(obj, 'method').mockImplementation(() => 'spyOn 函数定义')

normalMockFn() === '函数定义'
mormalMockFn.mockReset()
normalMockFn() === undefined

spyOnMockFn() === 'spyOn 函数定义'
spyOnMockFn.mockRestore()
spyOnMockFn() === undefined

但是,mockRestore() 还会将对象上被 spyOn 的方法还原为原函数,即:

const obj = { method: () => '原函数' }
const spyOnMockFn = jest.spyOn(obj, 'method').mockImplementation(() => 'spyOn 函数定义')

spyOnMockFn.mockRestore()
obj.method() === '原函数'