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

如何基于多REST API响应测试React组件状态更新

测试基于多REST API调用更新状态的React组件

我想要测试一个基于多个REST API调用响应更新状态的React组件,以下是我的代码:

setupTests.js

import Enzyme from 'enzyme';
import EnzymeAdapter from '@wojtekmaj/enzyme-adapter-react-17';

Enzyme.configure({ adapter: new EnzymeAdapter() });

apiProvider.js

const BASE_URL = "https://jsonplaceholder.typicode.com";

const get = (url) => {
  return fetch(`${BASE_URL}/${url}`, {
    method: "GET"
  })
    .then((response) => {
      if (response.ok) {
        return response.json();
      } else {
        throw new Error("Something went wrong");
      }
    })
    .then((data) => {
      return data;
    })
    .catch(() => {
      const response = { ok: false };
      return response;
    });
};

export const apiProvider = { get };

apiService.js

import { apiProvider } from "./apiProvider";

const getData1 = () => {
  return apiProvider.get("todos/1");
};

const getData2 = () => {
  return apiProvider.get("todos/2");
};

export const apiService = {
  getData1,
  getData2,
};

App.js

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

function App() {
  const [isLoading, setIsLoading] = useState(true);
  const [data1, setData1] = useState([]);
  const [data2, setData2] = useState([]);
  const [hasData1Loaded, setHasData1Loaded] = useState(false);
  const [hasData2Loaded, setHasData2Loaded] = useState(false);

  useEffect(() => {
    if (hasData1Loaded && hasData2Loaded) {
      setIsLoading(false);
    }
  }, [hasData1Loaded, hasData2Loaded]);

  useEffect(() => {
    apiService.getData1().then((response) => {
      setData1(response);
      setHasData1Loaded(true);
    });
  }, []);

  useEffect(() => {
    apiService.getData2().then((response) => {
      setData2(response);
      setHasData2Loaded(true);
    });
  }, []);

  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";
import { act } from "react-dom/test-utils";

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

  test("renders learn react link", () => {
    const wrapper = mount(<App />);
    act(() => {
      new Promise((resolve) => setTimeout(resolve, 0));
    });
    wrapper.update();
    console.log(wrapper.debug());
    expect(wrapper.find(".App-link").text()).toBe("Learn React");
  });
});

正确的测试方案

当前测试未mock API请求,会触发真实网络调用,且异步处理逻辑不正确。以下是优化后的测试代码,通过mock API服务来模拟异步响应:

import React from "react";
import { mount } from "enzyme";
import App from "./App";
import { apiService } from "./apiService";
import { act } from "react-dom/test-utils";

// Mock apiService的所有方法,避免真实网络请求
jest.mock('./apiService', () => ({
  apiService: {
    getData1: jest.fn(),
    getData2: jest.fn()
  }
}));

describe("App", () => {
  beforeEach(() => {
    // 重置mock调用记录,避免测试间干扰
    jest.clearAllMocks();
  });

  test("初始渲染loading状态", () => {
    const wrapper = mount(<App />);
    expect(wrapper.find('div').text()).toBe('Loading....');
    expect(wrapper.find('.App-link').exists()).toBe(false);
  });

  test("两个API都成功后,隐藏loading并渲染内容", async () => {
    // 模拟API成功响应数据
    const mockData1 = { id: 1, title: "Todo 1" };
    const mockData2 = { id: 2, title: "Todo 2" };
    
    apiService.getData1.mockResolvedValue(mockData1);
    apiService.getData2.mockResolvedValue(mockData2);

    let wrapper;
    await act(async () => {
      wrapper = mount(<App />);
      // 等待所有异步请求完成
      await Promise.all([
        apiService.getData1(),
        apiService.getData2()
      ]);
      wrapper.update();
    });

    // 验证状态更新和DOM渲染结果
    expect(wrapper.find('div').text()).not.toBe('Loading....');
    expect(wrapper.find('.App-link').text()).toBe('Learn React');
    expect(apiService.getData1).toHaveBeenCalledTimes(1);
    expect(apiService.getData2).toHaveBeenCalledTimes(1);
  });

  test("单个API失败时,仍能完成状态更新并渲染内容", async () => {
    // 模拟其中一个API失败的响应
    apiService.getData1.mockResolvedValue({ ok: false });
    apiService.getData2.mockResolvedValue({ id: 2, title: "Todo 2" });

    let wrapper;
    await act(async () => {
      wrapper = mount(<App />);
      await Promise.all([
        apiService.getData1(),
        apiService.getData2()
      ]);
      wrapper.update();
    });

    expect(wrapper.find('div').text()).not.toBe('Loading....');
    expect(wrapper.find('.App-link').exists()).toBe(true);
  });
});

测试逻辑说明

  • 用jest.mock完全mock apiService,避免真实网络请求
  • 每个测试前重置mock,保证测试独立性
  • 使用act包裹异步操作,确保React状态更新完成后再执行断言
  • 覆盖初始加载、双API成功、单API失败等核心场景,验证组件状态和DOM渲染逻辑

内容的提问来源于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 16:27:36