React Testing Library测试自定义Hook中Mock API调用失败求助
自定义Hook useGetUsers测试失败:无法进入API调用的.then()分支
我编写了一个名为useGetUsers的自定义Hook,在其useEffect函数中调用API并将结果存入状态,代码如下:
export const useGetUsers = (searchParams?: any | undefined, userId?: string) => { const [users, setUsers] = useState<{ data: readonly any[] | null; loading: boolean; }>({ data: [], loading: true, }); const parsedSearchParams = { limit: 100, ...(searchParams || {}), }; const searchParamStr = `?${makeQueryStringFromObject(parsedSearchParams)}`; useEffect(() => { userRequest('users', 'get', null, searchParamStr) .then(result => { setUsers({ data: result?.data, loading: false, }); }) .catch(() => { setUsers({ data: null, loading: false, }); }); }, [userId, searchParamStr]); return { users, setUsers }; };
我希望测试能进入API调用的.then()分支,但实际却进入了.catch()分支。尝试了两种测试写法均未成功,也试过waitForNextUpdate,依然无效:
第一种测试代码
test('when the call is a success', async () => { const spy = jest.spyOn(ES, 'userRequest'); const returnPromise = Promise.resolve({data: ['a']}) ES.userRequest = jest.fn(() => returnPromise); const { result, waitFor} = renderHook(() => useGetUsers()); await act(() => returnPromise) await waitFor(() => expect(spy).toHaveBeenCalled())//this fails });
第二种测试代码
test('when the call is a success', async () => { jest.mock('src/..', () => ({ ...jest.requireActual('src/..'), userRequest: jest .fn() .mockImplementation(() => new Promise(resolve => resolve({data: ['a']}))), })); const { result, waitFor} = renderHook(() => useGetUsers()); await waitFor(() => expect(ES.userRequest).toHaveBeenCalled()) });
问题修复方案
1. 正确Mock userRequest 模块
jest.mock必须放在测试文件的顶部作用域(不能放在单个test函数内部),否则无法在模块加载前完成替换:
// 放在测试文件最顶部,替换成userRequest实际所在的路径 jest.mock('src/path/to/userRequestModule', () => ({ ...jest.requireActual('src/path/to/userRequestModule'), userRequest: jest.fn().mockResolvedValue({ data: ['a'] }) })); import { userRequest } from 'src/path/to/userRequestModule'; import { useGetUsers } from './path/to/useGetUsers'; import { renderHook, waitFor } from '@testing-library/react';
2. 修复Spy写法(第一种测试的修正版)
第一种测试中,直接覆盖ES.userRequest会导致之前创建的spy失效,改用mockImplementation替换原有实现:
test('when the call is a success', async () => { const mockResponse = Promise.resolve({ data: ['a'] }); // 用mockImplementation替换实现,保留spy引用 jest.spyOn(ES, 'userRequest').mockImplementation(() => mockResponse); const { result } = renderHook(() => useGetUsers()); // 等待状态更新(loading变为false),而非直接执行promise await waitFor(() => { expect(result.current.users.loading).toBe(false); expect(result.current.users.data).toEqual(['a']); }); // 验证API调用参数 expect(ES.userRequest).toHaveBeenCalledWith('users', 'get', null, expect.any(String)); });
3. 关键排查点
- 检查
makeQueryStringFromObject函数是否在测试环境中正常工作:如果该函数抛出错误,会直接导致useEffect中的Promise进入catch分支 - 优先通过状态变化(如
loading: false)来验证测试结果,而非仅检查函数是否被调用,这样能覆盖更多执行路径 - 确保所有依赖模块的Mock在测试执行前完成,避免模块加载顺序导致Mock失效
内容的提问来源于stack exchange,提问作者Samira Arabgol
相关产品推荐
相关产品推荐

