如何使用Jest和Enzyme测试含多个API调用的useEffect
Jest + Enzyme 测试含串行API调用的useEffect方案
核心思路
你要测的逻辑核心是三点:组件挂载触发loadData、两个API按顺序串行调用、接口返回后正确更新对应state。全程不需要发真实网络请求,用Jest的mock能力替换API实现即可,mock函数自带spy统计能力,可以直接校验调用次数、入参是否符合预期。
具体实现步骤
1. 基础Mock配置
首先在测试文件顶部直接mock两个API模块,Jest会自动替换文件里导入的真实API实现,不会发起真实请求:
// 对应两个API文件的相对路径 jest.mock('../fetchCountry'); jest.mock('../fetchState');
这种mock方式生成的函数自带调用记录、返回值自定义能力,和jest.spyOn的spy效果完全一致,不需要额外包装。如果你需要保留模块部分原有实现,也可以用jest.spyOn单独监听默认导出的函数,写法后面会提。
2. 编写测试用例
注意两个关键细节:
- 必须用
mount全量挂载组件,旧版Enzyme的shallow浅挂载不会触发useEffect执行 - 异步逻辑执行需要等微任务队列清空,再刷新组件渲染树,否则断言会拿不到更新后的状态
完整测试代码示例:
import React from 'react'; import { mount } from 'enzyme'; import App from './App'; // 导入被mock的API函数,用于配置返回值、校验调用逻辑 import fetchCountry from '../fetchCountry'; import fetchState from '../fetchState'; describe('App初始化加载逻辑', () => { // 每个用例执行前重置mock状态,避免用例间数据污染 beforeEach(() => { jest.resetAllMocks(); // 模拟接口返回结构,注意fetchState依赖fetchCountry返回的code字段,值要对应 fetchCountry.mockResolvedValue({ code: 'JP', name: 'Japan' }); fetchState.mockResolvedValue([ { name: 'Tokyo', code: 'TK' }, { name: 'Osaka', code: 'OS' } ]); }); it('组件挂载后按顺序调用两个API', async () => { mount(<App />); // 等待所有await异步逻辑执行完毕 await Promise.resolve(); // 校验fetchCountry调用:入参正确、仅调用1次 expect(fetchCountry).toHaveBeenCalledTimes(1); expect(fetchCountry).toHaveBeenCalledWith('Japan'); // 校验fetchState调用:入参是上一个接口返回的国家码、在fetchCountry执行后调用 expect(fetchState).toHaveBeenCalledTimes(1); expect(fetchState).toHaveBeenCalledWith('JP'); }); it('接口返回后正确更新国家和州的状态', async () => { const wrapper = mount(<App />); await Promise.resolve(); // 刷新组件渲染树,拿到状态更新后的结果 wrapper.update(); // 优先校验DOM渲染结果(最符合测试行为而非测试实现的原则,函数组件也兼容) // 举个例子,如果页面渲染了国家名和州列表,可以这么写: // expect(wrapper.find('.country-name').text()).toBe('Japan'); // expect(wrapper.find('.state-item')).toHaveLength(2); // 如果你用的是类组件,可以直接通过Enzyme的state()方法读取内部状态断言: // expect(wrapper.state().country).toEqual({ code: 'JP', name: 'Japan' }); // expect(wrapper.state().state).toEqual([ // { name: 'Tokyo', code: 'TK' }, // { name: 'Osaka', code: 'OS' } // ]); }); it('国家接口请求失败时不会调用州接口', async () => { // 覆盖默认mock,模拟接口报错 fetchCountry.mockRejectedValue(new Error('获取国家信息失败')); // 屏蔽测试运行时的console报错输出 const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); mount(<App />); await Promise.resolve(); expect(fetchState).not.toHaveBeenCalled(); // 恢复原始console方法 errorSpy.mockRestore(); }); });
常见问题说明
- 如果需要用spyOn的写法监听API而不是全量mock模块,可以参考以下写法,适合需要保留模块部分真实逻辑的场景:
import * as countryApi from '../fetchCountry'; const fetchCountrySpy = jest.spyOn(countryApi, 'default') .mockResolvedValue({ code: 'JP', name: 'Japan' }); // 用例执行完记得恢复原始实现,避免影响其他用例 afterEach(() => { fetchCountrySpy.mockRestore(); }); - 函数组件不要依赖Enzyme的
state()方法读状态:Enzyme原生不支持直接读取useState定义的内部状态,断言最终渲染的DOM结果是最稳妥的方式,也更符合前端测试的最佳实践。 - 如果遇到useEffect不触发的问题,先检查Enzyme对应的React适配器版本是否匹配,必须保证适配器和你项目里的React大版本一致。
内容的提问来源于stack exchange,提问作者micronyks
相关产品推荐
相关产品推荐

