React TypeScript项目Jest测试无法找到模拟Fetch返回文本问题
React useFetch钩子测试失败问题排查与解决
我用Vite搭建了一个React TypeScript小型应用,已安装所有所需的React测试包。编写了一个通过自定义useFetch钩子调用JSONPlaceholder接口并渲染文章列表的组件,Jest测试已经解决了fetch未定义的问题,但运行测试时无法找到模拟Fetch返回的文本,且组件显示“Unable to fetch data”错误。
相关代码
useFetch钩子代码
import { useEffect, useState } from "react"; function useFetch<T>(url: string) { const [apiData, setApiData] = useState<T>(); const [pending, setPending] = useState(false); const [serverError, setServerError] = useState(false); useEffect(() => { setPending(true); fetch(url, { method: "GET", }) .then((res) => { if (!res.ok) { throw Error("Unable to fetch data"); } return res.json(); }) .then((data: T) => { setApiData(data); setPending(false); }) .catch((err) => { setPending(false); setServerError(err.message); }); }, [url]); return { apiData, pending, serverError }; } export default useFetch;
组件代码
import useFetch from "./useFetch"; import { IPosts } from "./IPosts"; export default function APIDemo() { const { apiData, pending, serverError } = useFetch<IPosts[]>( "https://jsonplaceholder.typicode.com/posts" ); return ( <div> <h1>POSTS</h1> {pending && <p>Loading...</p>} {serverError && <p>{serverError}</p>} <> {apiData?.map((post: IPosts) => ( <div key={post.id}> <h1>{post.title}</h1> <p>{post.body}</p> </div> ))} </> </div> ); }
测试代码(原版本)
import { act, render, screen, waitFor } from "@testing-library/react"; import APIDemo from "./APIDemo"; describe("API Demo", () => { beforeEach(() => { global.fetch = jest.fn().mockImplementation(() => Promise.resolve({ status: 200, json: () => Promise.resolve([ { id: 1, title: "Something1", body: "body1" }, { id: 2, title: "Something2", body: "body2" }, ]), }) ); }); it("Should render heading POSTS", async () => { render(<APIDemo />); await waitFor(() => { screen.getByText("POSTS"); }); }); it("Should render mocked post", async () => { await act(async () => render(<APIDemo />)); expect(screen.getByText("Something")).toBeInTheDocument(); }); });
测试失败结果
FAIL src/APIDemo.test.tsx API Demo √ Should render heading POSTS (44 ms) × Should render mocked post (10 ms) API Demo › Should render mocked post TestingLibraryElementError: Unable to find an element with the text: Something. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <div> <h1> POSTS </h1> <p> Unable to fetch data </p> </div> </div> </body> 56 | it("Should render mocked post", async () => { 57 | await act(async () => render(<APIDemo />)); > 58 | expect(screen.getByText("Something")).toBeInTheDocument(); | ^ 59 | }); 60 | }); 61 | at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19) at node_modules/@testing-library/dom/dist/query-helpers.js:76:38 at node_modules/@testing-library/dom/dist/query-helpers.js:52:17 at node_modules/@testing-library/dom/dist/query-helpers.js:95:19 at Object.<anonymous> (src/APIDemo.test.tsx:58:19) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 passed, 2 total Snapshots: 0 total Time: 3.505 s Ran all test suites.
问题原因
- 模拟的Response缺少
ok属性:useFetch钩子中通过res.ok判断请求是否成功,而你模拟的fetch返回对象只设置了status:200,没有显式定义ok: true,导致res.ok默认是false,直接触发了错误抛出,组件因此显示“Unable to fetch data”。 - 查询文本不匹配:测试用例中查询的是
"Something",但模拟数据里的标题是"Something1"和"Something2",不存在对应的文本。 - 不必要的
act包裹:React Testing Library的render方法已经内部处理了act逻辑,额外包裹反而可能导致异步状态处理异常。
修正后的测试代码
import { render, screen, waitFor } from "@testing-library/react"; import APIDemo from "./APIDemo"; describe("API Demo", () => { beforeEach(() => { global.fetch = jest.fn().mockImplementation(() => Promise.resolve({ status: 200, ok: true, // 必须添加该属性,确保res.ok为true json: () => Promise.resolve([ { id: 1, title: "Something1", body: "body1" }, { id: 2, title: "Something2", body: "body2" }, ]), }) ); }); it("Should render heading POSTS", async () => { render(<APIDemo />); await waitFor(() => { screen.getByText("POSTS"); }); }); it("Should render mocked post", async () => { render(<APIDemo />); // 使用findByText异步查询,匹配模拟数据中的实际文本 const postTitle = await screen.findByText("Something1"); expect(postTitle).toBeInTheDocument(); }); });
关键说明
- 添加
ok: true到模拟的Response对象,让useFetch的请求成功判断通过,正常返回数据。 - 调整查询文本为模拟数据中存在的
"Something1",确保能找到对应元素。 - 去掉不必要的
act包裹,改用findByText(内置异步等待逻辑),符合React Testing Library的最佳实践。
内容的提问来源于stack exchange,提问作者exlnt
相关产品推荐
相关产品推荐

