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

如何测试包含异步代码的自定义React Hook?

问题:测试自定义异步Hook时,始终得到[null]结果,无法等待fetch完成

我写了一个通过GET请求获取数据的自定义Hook,尝试测试它时,实际得到的响应是包含null的数组[null],测试失败。

自定义Hook代码

import { useState, useEffect } from "react";

export const useData = (url) => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => setData(data));
  }, [url]);

  return [data];
};

测试代码

import { renderHook, waitFor } from "@testing-library/react";
import { useData } from "./useData";

test("fetching the jsonarray", async () => {

  const { result } = renderHook(() =>
    useData("https://jsonplaceholder.typicode.com/todos?_limit=1")
  );

  let actualResponse;

  await waitFor(() => {
    actualResponse = result.current;
  });

  expect(actualResponse).toEqual([
    {
      userId: 1,
      id: 1,
      title: "delectus aut autem",
      completed: false,
    },
  ]);
});

测试失败结果

Expected  - 6
    + Received  + 1

      Array [
    -   Object {
    -     "completed": false,
    -     "id": 1,
    -     "title": "delectus aut autem",
    -     "userId": 1,
    -   },
    +   null,
      ]

解决方法

1. 给waitFor添加等待断言条件

你当前的waitFor只是赋值操作,没有设置等待的终止条件,所以它会立即执行,直接拿到Hook初始的[null]结果就结束了。需要让waitFor等待数据不为null:

修改测试代码:

test("fetching the jsonarray", async () => {
  const { result } = renderHook(() =>
    useData("https://jsonplaceholder.typicode.com/todos?_limit=1")
  );

  // 等待数据加载完成(不为null)
  await waitFor(() => {
    expect(result.current[0]).not.toBeNull();
  });

  // 此时再获取结果进行断言
  const actualResponse = result.current;
  expect(actualResponse).toEqual([
    {
      userId: 1,
      id: 1,
      title: "delectus aut autem",
      completed: false,
    },
  ]);
});

2. Mock fetch请求(推荐)

真实网络请求受环境影响,测试稳定性差,建议用Jest模拟fetch,隔离外部依赖:

import { renderHook, waitFor } from "@testing-library/react";
import { useData } from "./useData";

// 测试前模拟fetch
beforeEach(() => {
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve({
        userId: 1,
        id: 1,
        title: "delectus aut autem",
        completed: false,
      }),
    })
  );
});

// 测试后恢复原fetch
afterEach(() => {
  jest.restoreAllMocks();
});

test("fetching the jsonarray", async () => {
  const { result } = renderHook(() => useData("任意URL"));

  await waitFor(() => {
    expect(result.current[0]).not.toBeNull();
  });

  expect(result.current).toEqual([
    {
      userId: 1,
      id: 1,
      title: "delectus aut autem",
      completed: false,
    },
  ]);
  // 还可以断言fetch被调用过
  expect(fetch).toHaveBeenCalledTimes(1);
});

3. 给Hook添加错误处理(可选)

原Hook没有错误捕获逻辑,若请求失败会导致测试超时或卡住。可以优化Hook:

import { useState, useEffect } from "react";

export const useData = (url) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(url)
      .then((res) => {
        // 检查响应状态
        if (!res.ok) throw new Error(`请求失败:${res.status}`);
        return res.json();
      })
      .then((data) => setData(data))
      .catch((err) => setError(err.message));
  }, [url]);

  return [data, error];
};

测试时也可以断言错误状态,让测试覆盖更多场景。

内容的提问来源于stack exchange,提问作者Gautham Muralidharan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 11:57:31