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

React测试:如何Mock useState以覆盖Spinner组件代码

如何Mock useState的isLoading值以测试Spinner组件

你的测试代码存在的核心问题

  1. useState返回格式错误:React的useState返回的是数组,不是对象,但你mock时返回了{isLoading, setLoading},和组件里的数组解构const [isLoading, setLoading] = useState(false)不匹配,导致组件无法正确获取状态。
  2. 手动调用setLoading无法触发重渲染:组件完成初始渲染后,直接调用mock的setLoading(true)不会触发组件重新渲染,自然找不到Spinner元素。

推荐方案:模拟真实用户操作(符合测试最佳实践)

不需要直接mock useState,而是模拟用户点击搜索按钮,同时mock API请求让它处于pending状态,就能捕获到loading状态下的Spinner组件。

测试代码示例

import { render, fireEvent, waitFor } from "@testing-library/react";
import { Search } from "../../components/search/search";
import { BrowserRouter as Router } from "react-router-dom";
import movieAPI from "../../services/movieAPI";

// 提前mock movieAPI模块
jest.mock("../../services/movieAPI");

describe("Search组件", () => {
  it("点击搜索按钮后显示Spinner", async () => {
    // 让fetchMovieByTitle返回pending状态的Promise,模拟加载中
    (movieAPI.fetchMovieByTitle as jest.Mock).mockImplementation(() => new Promise(() => {}));

    const { getByText, getByTestId } = render(
      <Router>
        <Search />
      </Router>
    );

    // 模拟用户输入电影名
    fireEvent.change(getByPlaceholderText("Search"), { target: { value: "Inception" } });
    // 模拟点击搜索按钮
    fireEvent.click(getByText("Search"));

    // 等待Spinner出现
    const spinner = await waitFor(() => getByTestId("spinner"));
    expect(spinner).toBeInTheDocument();
  });
});

备选方案:直接控制isLoading状态(适合特殊场景)

如果确实需要手动控制isLoading的值,需要正确mock useState,区分组件中两次useState的调用(一次是movie,一次是isLoading),并确保状态更新能触发组件重渲染。

测试代码示例

import { render, waitFor } from "@testing-library/react";
import { Search } from "../../components/search/search";
import { BrowserRouter as Router } from "react-router-dom";
import React from "react";

describe("Search组件", () => {
  let isLoadingState = false;
  const originalUseState = React.useState;

  beforeEach(() => {
    isLoadingState = false;
    // 自定义mock useState逻辑
    jest.spyOn(React, "useState").mockImplementation((initialValue) => {
      // 区分两次useState调用:第一次是movie的初始值"",第二次是isLoading的初始值false
      if (initialValue === false) {
        return [
          isLoadingState,
          (newValue) => {
            isLoadingState = newValue;
            // 强制触发组件重渲染
            React.useState.mockImplementation(originalUseState);
            render(<Router><Search /></Router>);
            React.useState.mockImplementation(mockUseState);
          },
        ];
      }
      // 其他useState调用返回原始行为
      return originalUseState(initialValue);
    });
  });

  afterEach(() => {
    // 恢复原始useState
    React.useState.mockRestore();
  });

  it("当isLoading为true时显示Spinner", async () => {
    const { getByTestId } = render(<Router><Search /></Router>);

    // 手动设置isLoading为true
    const [, setLoading] = React.useState(false);
    setLoading(true);

    // 等待Spinner出现
    const spinner = await waitFor(() => getByTestId("spinner"));
    expect(spinner).toBeInTheDocument();
  });
});

关键提示

  • 优先使用模拟真实用户操作的方案,因为它更贴近实际用户行为,测试结果更可靠。
  • 直接mock useState的方式依赖组件中useState的调用顺序,一旦组件内useState顺序改变,测试就会失效,需谨慎使用。

内容的提问来源于stack exchange,提问作者Lucas Fernando

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 16:40:39