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

测试useEffect内结合API调用的useState组件失败问题排查

React组件测试中setValue与setLoading未生效导致测试失败的解决方法

问题

测试React组件时,异步请求后的setValue和setLoading未触发,组件始终显示初始占位符数据和加载状态,导致测试断言失败。

组件代码

const [value, setValue] = useState({
  street_name: '-',
  street_number: '-',
  city: '-',
  zip_code: '-',
  comment: '-',
});
const [loading, setLoading] = useState(true);

useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await getAddressById(packageId);
      setValue(response);
      setLoading(false);
    } catch (error) {
      console.error(error);
    }
  };
  fetchData();
}, []);

return (
  <>
    <Spinner
      show={loading}
      size="large"
    />
    <TableData data-title="ID">{packageId}</TableData>
    <TableData data-title="Calle">{value.street_name}</TableData>
    <TableData data-title="Número">{value.street_number}</TableData>
    <TableData data-title="Ciudad">{value.city}</TableData>
    <TableData data-title="CP">{value.zip_code}</TableData>
    <TableData data-title="Referencia">{value.comment}</TableData>
  </>
);

原测试代码

it('should render the component', async () => {

  jest.mock("../../../../../../../services", () => ({
    getAddressById: jest.fn(() => Promise.resolve({
      street_name: 'street_name',
      street_number: 'street_number',
      city: 'city',
      zip_code: 'zip_code',
      comment: 'comment',
    })),
  }));

  const props = {
   packageId: '11111',
  };

  await act(async () => {
    const { container } = await render(<Item {...props} />);
    expect(screen.getByText(props.packageId)).toBeInTheDocument();
    expect(screen.getByText('street_name')).toBeInTheDocument();
  });
});

问题根源

  1. jest.mock放置位置错误:jest.mock必须放在测试文件顶部(组件导入语句之前),因为jest在模块加载阶段替换mock。放在it块内部时,组件已导入原始的getAddressById方法,导致mock不生效,异步请求未按预期返回数据,setValue和setLoading无法触发。
  2. useEffect依赖项缺失:useEffect依赖数组为空,但内部使用了packageId props,可能导致props变化时无法重新获取数据,也会触发ESLint警告影响稳定性。
  3. 断言时机不对:直接在act内部断言异步渲染的元素,可能在状态更新完成前执行,导致找不到目标元素。

修复方案

1. 调整jest.mock位置

将jest.mock移到测试文件最顶部,确保组件导入前完成mock:

// 放在文件最顶部,组件导入之前
jest.mock("../../../../../../../services", () => ({
  getAddressById: jest.fn(() => Promise.resolve({
    street_name: 'street_name',
    street_number: 'street_number',
    city: 'city',
    zip_code: 'zip_code',
    comment: 'comment',
  })),
}));

import { render, screen, waitFor } from '@testing-library/react';
import Item from './path-to-your-component';

it('should render the component', async () => {
  const props = {
    packageId: '11111',
  };

  render(<Item {...props} />);
  
  // 等待异步数据渲染完成
  await waitFor(() => {
    expect(screen.getByText(props.packageId)).toBeInTheDocument();
    expect(screen.getByText('street_name')).toBeInTheDocument();
  });
});

2. 修复useEffect依赖数组

添加packageId到依赖数组,确保props变化时重新触发数据获取:

useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await getAddressById(packageId);
      setValue(response);
      setLoading(false);
    } catch (error) {
      console.error(error);
    }
  };
  fetchData();
}, [packageId]); // 添加packageId到依赖数组

3. 使用waitFor等待异步操作

waitFor会自动包裹在act中,专门用于等待异步更新后的断言,比手动使用act更可靠。

额外验证

可以在测试中添加mock调用验证,确保服务被正确触发:

import { getAddressById } from "../../../../../../../services";

// ... 在测试用例中
await waitFor(() => {
  expect(getAddressById).toHaveBeenCalledWith('11111');
});

内容的提问来源于stack exchange,提问作者Luiz Miranda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 08:48:09