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
相关产品推荐
相关产品推荐

