如何在Jest中保留可用的手动Mock并兼容CRA的resetMocks新默认值?
resetMocks: true导致手动Mock失效的问题 我刚好踩过这个坑!CRA 4.0.0默认开启的resetMocks: true确实会搞砸你在__mocks__里定义的手动Mock,导致Jest抛出mockConstructor(...): Nothing was returned from render的错误。不过别担心,我们可以找到一个两全其美的方案——既保留手动Mock的功能,又能享受resetMocks带来的测试用例间状态隔离优势。
问题根源
resetMocks: true的作用是在每个测试用例执行后,重置所有Jest mock的状态——包括把mock函数的实现重置为空。你的__mocks__/SomeComponent.js里是用jest.fn().mockImplementation(...)来定义Mock组件的,所以每次测试结束后,这个mock的实现会被清空,下一个测试运行时,组件渲染就会返回undefined,自然报错。
完美解决方案:让Mock自动恢复实现
我们只需要修改手动Mock的写法,让它在每次测试前自动恢复自己的实现,同时让resetMocks继续帮我们清理调用次数、参数这些状态。
修改__mocks__/SomeComponent.js
把原来的Mock代码改成这样:
// src/__mocks__/SomeComponent.js const implementation = () => <p>mocked SomeComponent</p> // 初始化mock时直接绑定实现 const SomeComponent = jest.fn(implementation) // 每次测试前,重新绑定实现(应对resetMocks的重置) beforeEach(() => { SomeComponent.mockImplementation(implementation) }) export default SomeComponent
这样一来,不管resetMocks怎么重置mock的状态,我们都会在测试开始前把实现给补回去,Mock组件就能正常渲染了。
优化测试文件(可选)
另外,你原来测试文件里的beforeEach调用mockClear()其实可以删掉了——因为resetMocks: true已经会自动帮我们重置mock的调用次数、参数等状态,完全覆盖了mockClear()的功能:
// src/App.test.js import { render, screen } from '@testing-library/react' import App from './App' import SomeComponent from './SomeComponent' jest.mock('./SomeComponent') // 删掉这部分,resetMocks已经处理了 // beforeEach(() => { // SomeComponent.mockClear() // }) it('should contain mocked component', () => { render(<App />) screen.getByText(/mocked SomeComponent/i) }) it('should have correct calledTimes', () => { render(<App />) screen.getByText(/mocked SomeComponent/i) expect(SomeComponent).toHaveBeenCalledTimes(1) })
备选方案:局部禁用resetMocks(不推荐)
如果你不想修改所有Mock文件,也可以在单个测试文件里禁用resetMocks,但这样会失去测试间的状态隔离,需要手动清理mock状态,所以不如上面的方案优雅:
// 在测试文件顶部添加,仅当前文件禁用resetMocks jest.config.resetMocks = false;
但这种方式下,你需要手动在beforeEach里调用mockClear()或者mockReset()来清理状态,不然测试用例间会互相影响。
验证效果
修改完之后运行测试,你会发现两个测试都能顺利通过:
- 第一个测试正确渲染了Mock组件的内容
- 第二个测试里
calledTimes依然是1,说明resetMocks依然在正常工作,清理了上一个测试的调用记录
这样就既保住了你的手动Mock,又没浪费CRA新默认配置带来的测试隔离优势~
内容的提问来源于stack exchange,提问作者ian-s-mcb

