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

React组件状态更新测试问题(Jest+Enzyme环境)

问题:Jest+Enzyme测试React组件时无法捕获状态更新后的内容

我用Jest和Enzyme测试React组件,想要验证状态更新后的文本内容,但运行测试时控制台始终显示加载状态的组件,无法获取到加载完成后的内容。相关代码如下:

App.js代码

import logo from "./logo.svg";
import "./App.css";
import React, { useEffect } from "react";

function App() {
  const [isLoading, setIsLoading] = React.useState(true);
  const [isDataLoaded, setIsDataLoaded] = React.useState(false);

  useEffect(() => {
    if (isDataLoaded) {
      setIsLoading(false);
    }
  }, [isDataLoaded]);

  useEffect(() => {
    setTimeout(() => setIsDataLoaded(true), 4000);
  }, []);

  return (
    <div className="App">
      {isLoading && <div>Loading....</div>}
      {!isLoading && (
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      )}
    </div>
  );
}

export default App;

App.test.js代码

import React from "react";
import { mount } from "enzyme";
import App from "./App";

let wrapper;

const setup = (state = {}) => {
  const mockUseState = jest.fn().mockReturnValue([{ ...state }, jest.fn()]);
  React.useState = mockUseState;
  return mount(<App />);
};

// beforeEach(() => {
//   wrapper = setup();
// });

// afterEach(() => {
//   wrapper.unmount();
// });

describe("App", () => {
  test("renders without error", () => {
    wrapper = setup();
    expect(wrapper.find("App").exists()).toBe(true);
  });

  test("renders learn react link", () => {
    wrapper = setup({ isLoading: false });
    wrapper.setProps();
    console.log(wrapper.debug(), "console output")
    expect(wrapper.find("App-link").text()).toBe("Learn React");
  });
});

问题分析

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

  1. 错误mock多个useState调用:组件里有两个独立的useState,但你的mockUseState只返回一组状态,导致第二个useState(isDataLoaded)被错误覆盖,破坏了组件原有的状态关联逻辑。
  2. 跳过真实生命周期逻辑:直接传入isLoading: false模拟状态,没有触发组件依赖的useEffect和setTimeout流程,测试的不是组件的实际运行行为。

解决方案

方案一:模拟定时器,贴近真实组件流程(推荐)

通过Jest的定时器模拟,让setTimeout立即执行,触发组件完整的状态更新流程:

import React from "react";
import { mount } from "enzyme";
import App from "./App";

describe("App", () => {
  let wrapper;

  beforeEach(() => {
    // 启用定时器模拟
    jest.useFakeTimers();
    wrapper = mount(<App />);
  });

  afterEach(() => {
    wrapper.unmount();
    // 恢复真实定时器
    jest.useRealTimers();
  });

  test("renders without error", () => {
    expect(wrapper.find(".App").exists()).toBe(true);
  });

  test("renders learn react link after loading completes", () => {
    // 初始状态验证加载文本
    expect(wrapper.text()).toContain("Loading....");

    // 快进所有定时器,触发setIsDataLoaded(true)
    jest.runAllTimers();
    // 强制组件更新,确保Enzyme捕获状态变更后的DOM
    wrapper.update();

    // 验证加载完成后的内容
    expect(wrapper.find(".App-link").text()).toBe("Learn React");
    expect(wrapper.text()).not.toContain("Loading....");
  });
});

方案二:正确mock多个useState调用

如果需要直接指定状态值,需分别模拟两个useState的返回值:

import React from "react";
import { mount } from "enzyme";
import App from "./App";

describe("App", () => {
  test("renders learn react link when isLoading is false", () => {
    // 分别模拟两个useState的返回值
    jest.spyOn(React, 'useState')
      .mockReturnValueOnce([false, jest.fn()]) // 第一个useState: isLoading
      .mockReturnValueOnce([true, jest.fn()]); // 第二个useState: isDataLoaded

    const wrapper = mount(<App />);
    expect(wrapper.find(".App-link").text()).toBe("Learn React");

    // 清除mock,避免影响其他测试
    React.useState.mockRestore();
  });
});

内容的提问来源于stack exchange,提问作者Sunil tc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 09:33:15