如何基于多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完全mockapiService,避免真实网络请求 - 每个测试前重置mock,保证测试独立性
- 使用
act包裹异步操作,确保React状态更新完成后再执行断言 - 覆盖初始加载、双API成功、单API失败等核心场景,验证组件状态和DOM渲染逻辑
内容的提问来源于stack exchange,提问作者Sunil tc
相关产品推荐
相关产品推荐

