使用Jest测试React自定义图片加载Hook时,如何触发onload函数?
解决useImage Hook中onload函数无法被Jest测试的问题
问题背景
我尝试测试用于加载图片的自定义Hook的onload函数,但运行测试时onload被跳过。
自定义Hook代码
import { useEffect, useState } from "react"; const useImage = (src: string) => { const [loaded, setLoaded] = useState(false); useEffect(() => { const image = new Image(); image.src = src; image.onload = () => { return setLoaded(true); // 无法触发该逻辑 }; }, [src]); return { loaded }; }; export default useImage;
测试代码
import { renderHook, waitFor, } from "@testing-library/react"; import useImage from "./useImage"; describe("useImage", () => { it("Loads a data url into an image element", async () => { const img = new Image(); img.src = "foo"; const { result } = renderHook(() => useImage("foo")); await waitFor(() => expect(result.current).toStrictEqual({ loaded: true }) // loaded始终为false ); }); });
解决方案
Jest运行在Node.js环境中,没有真实浏览器的图片加载能力,原生Image对象的onload事件不会自动触发,需要通过Mock的方式手动触发或模拟加载完成。
方案一:手动捕获并触发onload回调
通过Mock全局Image构造函数,记录Hook中设置的onload回调,随后手动调用它:
import { renderHook, waitFor } from "@testing-library/react"; import useImage from "./useImage"; describe("useImage", () => { it("triggers onload and sets loaded to true", async () => { // 保存原始Image构造函数,测试后恢复 const OriginalImage = global.Image; let mockOnload: (() => void) | undefined; // Mock Image类 global.Image = jest.fn(() => ({ set src(_: string) { // 不执行真实的图片加载逻辑 }, set onload(callback: () => void) { // 记录onload回调 mockOnload = callback; }, })) as typeof OriginalImage; const { result } = renderHook(() => useImage("foo")); // 手动触发onload回调 mockOnload?.(); await waitFor(() => { expect(result.current.loaded).toBe(true); }); // 恢复原始Image构造函数 global.Image = OriginalImage; }); });
方案二:模拟图片自动加载完成
MockImage构造函数,在实例创建后自动触发onload事件:
import { renderHook, waitFor } from "@testing-library/react"; import useImage from "./useImage"; describe("useImage", () => { it("sets loaded to true when image loads", async () => { // Mock Image,模拟图片立即加载完成 jest.spyOn(global, "Image").mockImplementation(() => { const imgInstance = {} as HTMLImageElement; // 用setTimeout模拟异步加载完成 setTimeout(() => imgInstance.onload?.(), 0); return imgInstance; }); const { result } = renderHook(() => useImage("foo")); await waitFor(() => { expect(result.current.loaded).toBe(true); }); // 恢复所有Mock jest.restoreAllMocks(); }); });
内容的提问来源于stack exchange,提问作者NewJr
相关产品推荐
相关产品推荐

