如何在Jest中测试含异步函数的useEffect Hook?
问题分析与解决方案
错误原因
- 异步时机未处理:
useEffect中的请求是异步执行的,测试时直接断言初始渲染的返回值,此时请求还未完成,拿到的是useRef的初始空数组。 - Ref更新不触发重渲染:用
useRef存储请求结果,更新current值不会触发组件重渲染,测试里的hook.current始终停留在初始值。 - 测试逻辑矛盾:测试用例描述是"should return empty array",但断言的却是期望返回
{ set: { a: 'b' } },逻辑不匹配。
修正Hook代码
原Hook用useRef存储数据的方式不合理,应该改用useState触发组件重渲染,让外部感知数据变化:
import { useState, useEffect } from 'react'; import fetchJsonp from 'fetch-jsonp'; import { getConfig } from './your-config-path'; // 替换为实际路径 const FETCH_URL = 'your-actual-fetch-url'; // 替换为实际请求地址 export const useDAMProductImages = (imageId: string) => { const { app: { baseImgDomain } } = getConfig(); const [images, setImages] = useState<string[]>([]); const [error, setError] = useState<string | null>(null); useEffect(() => { const getProductImages = async () => { if (!imageId) return; try { const url = new URL(FETCH_URL); // 根据需求添加请求参数,比如url.searchParams.append('imageId', imageId); const res = await fetchJsonp(url.href, { jsonpCallbackFunction: 'callback', }); const jsonData = await res.json(); setImages(jsonData as string[]); setError(null); } catch (err) { setImages([]); setError('图片加载失败'); } }; getProductImages(); }, [imageId]); return { images, error }; };
正确测试写法
使用waitFor等待异步操作完成,同时Mock依赖的getConfig函数:
import { renderHook, waitFor } from '@testing-library/react'; import { useDAMProductImages } from './useDAMProductImages'; import fetchJsonp from 'fetch-jsonp'; // Mock配置获取函数 jest.mock('./your-config-path', () => ({ getConfig: () => ({ app: { baseImgDomain: 'test-domain.com' } }) })); // Mock fetch-jsonp jest.mock('fetch-jsonp', () => jest.fn().mockImplementation(() => Promise.resolve({ status: 200, json: () => Promise.resolve(['img1.jpg', 'img2.jpg']), }), ), ); describe('useDAMProductImages', () => { beforeEach(() => { jest.clearAllMocks(); }); it('请求成功时返回正确的图片数组', async () => { const { result } = renderHook(() => useDAMProductImages('valid-id')); // 初始状态为空数组 expect(result.current.images).toEqual([]); // 等待异步请求完成后断言 await waitFor(() => { expect(result.current.images).toEqual(['img1.jpg', 'img2.jpg']); expect(result.current.error).toBeNull(); }); // 验证请求被调用 expect(fetchJsonp).toHaveBeenCalledTimes(1); }); it('请求失败时返回错误状态', async () => { // 重写Mock模拟请求失败 (fetchJsonp as jest.Mock).mockImplementation(() => Promise.reject(new Error('Fetch error')) ); const { result } = renderHook(() => useDAMProductImages('invalid-id')); await waitFor(() => { expect(result.current.images).toEqual([]); expect(result.current.error).toBe('图片加载失败'); }); }); it('当imageId为空时不发起请求', () => { renderHook(() => useDAMProductImages('')); expect(fetchJsonp).not.toHaveBeenCalled(); }); });
测试关键点
- 用
waitFor等待异步状态更新,避免过早断言。 - Mock所有外部依赖(
getConfig、fetchJsonp),确保测试环境独立。 - 覆盖多种场景:请求成功、失败、无有效
imageId的情况。
内容的提问来源于stack exchange,提问作者BlackWhite
相关产品推荐
相关产品推荐

