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

React组件Jest测试:点击触发fetch后回调未被检测到的问题

问题:Jest测试中异步回调函数断言失败

组件逻辑为点击按钮后发起API请求,请求完成后调用传入的onSuccess回调。测试中能正确断言fetch被调用,但始终无法断言onSuccess被执行。

代码示例

// @ts-nocheck
import { render, screen, fireEvent } from "@testing-library/react";
import jest from 'jest-mock';

const ClickTarget = ({ onSuccess }) => {
  async function handleClick() {
    try {
      const response = await fetch('/api/posts/');
      console.log(response);
    } catch (error) {
      console.error(error);
    }

    onSuccess('Done');
  }

  return (
    <button onClick={() => handleClick()} data-testid="saveBtn" type="button">
      Save
    </button>
  );
};

// JEST TEST
global.fetch = jest.fn(() =>
  Promise.resolve({ "content": "test" })
);

console.log = () => { };

describe("ClickTarget component", () => {

  it("should call API then callback", () => {
    const mockSuccessCb = jest.fn();

    render(<ClickTarget onSuccess={mockSuccessCb} />);

    const btn = screen.getByTestId('saveBtn');
    expect(btn).toBeInTheDocument();
    fireEvent.click(btn)
    expect(fetch).toHaveBeenCalledTimes(1);
    expect(fetch.mock.calls[0][0]).toContain("/api/posts/");

    // 测试失败点:onSuccess的断言不生效
    expect(mockSuccessCb).toHaveBeenCalledTimes(1);
    expect(mockSuccessCb.mock.calls[0][0]).toBe('Done');

  });
});

问题原因

handleClick是异步函数,点击按钮后,fetch返回的Promise会进入微任务队列,而测试代码是同步执行的——在断言onSuccess时,微任务还未执行,await fetch(...)后的代码(包括onSuccess调用)还没运行,因此断言会失败。

解决方案

将测试函数改为异步函数,用waitFor包裹对onSuccess的断言,等待微任务执行完成:

// 先确保导入waitFor
import { render, screen, fireEvent, waitFor } from "@testing-library/react";

// 修改测试用例
it("should call API then callback", async () => {
  const mockSuccessCb = jest.fn();

  render(<ClickTarget onSuccess={mockSuccessCb} />);

  const btn = screen.getByTestId('saveBtn');
  expect(btn).toBeInTheDocument();
  fireEvent.click(btn);
  
  expect(fetch).toHaveBeenCalledTimes(1);
  expect(fetch.mock.calls[0][0]).toContain("/api/posts/");

  // 用waitFor等待回调被调用
  await waitFor(() => {
    expect(mockSuccessCb).toHaveBeenCalledTimes(1);
    expect(mockSuccessCb.mock.calls[0][0]).toBe('Done');
  });
});

也可以通过手动刷新微任务队列实现(语义化不如waitFor,不推荐):

it("should call API then callback", () => {
  const mockSuccessCb = jest.fn();

  render(<ClickTarget onSuccess={mockSuccessCb} />);

  const btn = screen.getByTestId('saveBtn');
  fireEvent.click(btn);
  
  expect(fetch).toHaveBeenCalledTimes(1);

  // 等待微任务执行
  return Promise.resolve().then(() => {
    expect(mockSuccessCb).toHaveBeenCalledTimes(1);
    expect(mockSuccessCb.mock.calls[0][0]).toBe('Done');
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 08:10:02