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

使用react-testing-library模拟请求数据时组件始终处于加载状态的问题求助

解决React Hooks组件挂载数据获取测试中加载状态无法结束的问题

我在测试React组件的挂载数据获取逻辑时遇到了完全一样的困境——不管用Jest原生模拟fetch还是fetch-mock,组件始终卡在加载状态,快照里一直显示加载指示器,数据似乎从未完成加载。先贴出我的相关代码和尝试过的模拟方式,再一步步排查解决:

组件核心代码

const fetchData = () => {
  setIsLoading(true);
  fetch(url)
    .then(response => response.json())
    .then(data => {
      setData([data?.data]);
      setIsLoading(false);
    })
    .catch(error => {
      setIsLoading(false);
      console.error(error);
    });
};
useEffect(() => {
  fetchData();
}, []);

尝试过的两种模拟方式

1. Jest原生模拟fetch

const expectedResponse = { data: [{ id: 'id', name: 'name' }] };
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce(
  new Response(JSON.stringify(expectedResponse)),
);

2. 使用fetch-mock

import fetchMock from 'fetch-mock';
fetchMock.restore().get(ENDPOINT, {
  data: [
    { id: 'id', name: 'name' },
  ],
});

排查与解决建议

一、最常见原因:测试未等待异步操作完成

Jest默认同步执行测试逻辑,如果你直接渲染组件后立刻生成快照,此时fetch的Promise还未resolve,组件自然还停留在isLoading=true的状态。必须让测试等待异步状态更新完成:

推荐用React Testing Library的waitFor或findBy*系列查询(它们会自动等待异步操作),示例测试代码:

import { render, waitFor, screen } from '@testing-library/react';
import YourComponent from './YourComponent';

test('loads data successfully on mount', async () => {
  // 先配置fetch模拟
  const expectedResponse = { data: [{ id: 'id', name: 'name' }] };
  (fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce({
    json: jest.fn().mockResolvedValueOnce(expectedResponse),
  } as unknown as Response);

  render(<YourComponent />);

  // 等待加载状态消失
  await waitFor(() => {
    expect(screen.queryByText('加载中...')).not.toBeInTheDocument();
  });

  // 或者直接等待数据元素出现
  const dataItem = await screen.findByText('name');
  expect(dataItem).toBeInTheDocument();

  // 此时再生成快照才是最终状态
  expect(screen.container).toMatchSnapshot();
});

二、检查fetch模拟的正确性

针对Jest模拟的情况

原生Response对象在测试环境中可能存在兼容性问题,建议手动mockjson()方法,避免解析失败:

// 替换原来的Response模拟
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce({
  json: jest.fn().mockResolvedValueOnce(expectedResponse),
} as unknown as Response);

针对fetch-mock的情况

  • 确保ENDPOINT和组件中使用的url完全匹配(包括末尾斜杠、查询参数等)
  • 如果组件中使用的是第三方fetch库(比如cross-fetch),需要明确指定mock该库的fetch实例:
import fetch from 'cross-fetch';
import fetchMock from 'fetch-mock';

// 明确绑定要mock的fetch实例
fetchMock.mock(fetch).restore().get(ENDPOINT, {
  data: [{ id: 'id', name: 'name' }],
});

三、排查组件内部状态更新逻辑

检查setData([data?.data])是否符合预期:你模拟的expectedResponse.data已经是一个数组,setData([data?.data])会把状态变成二维数组[[{id: 'id', name: 'name'}]]。如果组件渲染时遍历的是data,可能因为数据结构错误导致渲染异常,但不会直接导致加载状态不结束——不过还是建议改成setData(data?.data)(如果业务逻辑需要的是一维数组)。

四、确认全局fetch的mock生效范围

如果组件中不是使用全局window.fetch,而是从第三方库导入的fetch(比如import fetch from 'whatwg-fetch'),需要在测试中mock对应的模块:

// 先mock整个模块
jest.mock('whatwg-fetch', () => jest.fn());
import fetch from 'whatwg-fetch';

// 再配置mock返回
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce(/* ... */);

内容的提问来源于stack exchange,提问作者marcus-linmarkson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 13:37:31