使用Sinon/Enzyme测试React函数组件时Mock自定义Hook失败怎么办
问题原因
你写的sinon stub不生效的核心原因:useCustomHook是通过ES模块import语法导入的独立函数,并不是TestComponent组件的实例/静态属性,sinon.stub(TestComponent, 'useCustomHook')根本找不到可替换的目标挂载点,自然无法注入你预设的模拟返回值。
解决方案
提供两种无需修改业务组件代码的实现方式:
方案1:配合babel-plugin-rewire直接mock导入的Hook
这个方案可以直接替换组件文件内导入的useCustomHook实现,逻辑直观:
- 先安装开发依赖
babel-plugin-rewire,并在项目babel配置中启用该插件 - 测试用例参考写法:
import TestComponent from './TestComponent'; import { mount } from 'enzyme'; import sinon from 'sinon'; import { expect } from 'chai'; describe('<TestComponent />', () => { afterEach(() => { // 用例执行完成后恢复原Hook实现,避免污染其他测试用例 TestComponent.__ResetDependency__('useCustomHook'); }); it('renders an error when receiving one from hook', () => { // 注入mock的Hook返回值 TestComponent.__Rewire__('useCustomHook', () => ({ data: [], error: 'test error' })); const wrapper = mount(<TestComponent />); expect(wrapper.find('#test-component-error')).to.have.lengthOf(1); expect(wrapper.find('#test-component-error').text()).to.equal('test error'); }); it('renders select options when hook returns valid data', () => { const mockData = [{ name: 'option1' }, { name: 'option2' }]; TestComponent.__Rewire__('useCustomHook', () => ({ data: mockData, error: null })); const wrapper = mount(<TestComponent />); expect(wrapper.find('select option')).to.have.lengthOf(2); }); });
方案2:不mock Hook,直接mock Hook依赖的Service请求
如果不想引入额外的babel插件,可以直接stub自定义Hook内部调用的Service类方法,通过控制接口返回结果覆盖所有渲染分支,仅用sinon就能实现:
import TestComponent from './TestComponent'; import Service from './Service'; import { mount } from 'enzyme'; import sinon from 'sinon'; import { expect } from 'chai'; describe('<TestComponent />', () => { let getDataStub; beforeEach(() => { // 每个用例执行前替换Service的getData方法 getDataStub = sinon.stub(Service.prototype, 'getData'); }); afterEach(() => { // 用例执行完恢复原方法 getDataStub.restore(); }); it('renders an error when api request fails', async () => { // 模拟接口报错 getDataStub.rejects('test error'); const wrapper = mount(<TestComponent />); // 等待异步请求执行完成、组件状态更新 await Promise.resolve(); wrapper.update(); expect(wrapper.find('#test-component-error')).to.have.lengthOf(1); }); it('renders select options when api request succeeds', async () => { const mockData = [{ name: 'option1' }, { name: 'option2' }]; // 模拟接口返回正常数据 getDataStub.resolves(mockData); const wrapper = mount(<TestComponent />); await Promise.resolve(); wrapper.update(); expect(wrapper.find('select option')).to.have.lengthOf(2); }); });
注意:该方案中Hook内部是异步请求数据,mount组件后需要等待微任务队列执行完成,再调用
wrapper.update()触发enzyme的视图重渲染,否则拿到的是组件初始渲染的空状态,断言会失败。
内容的提问来源于stack exchange,提问作者user19258133
相关产品推荐
相关产品推荐

