You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 22:15:47