如何在Vitest中等待RTK Query完成后测试React自定义Hook?
问题描述
我编写了名为useWelcomeMessage的React自定义Hook,内部通过RTK Query的useGetWelcomeMessage获取数据,并用useCallback封装了printMessage函数:当data存在时触发alert,error存在时抛出错误。
使用Vitest结合MSW编写测试用例时,期望验证alert能正确调用传入的mock数据,但调用printMessage时,useGetWelcomeMessage尚未执行完成,data仍为undefined。尝试假定时器无效,目前通过在MSW的get handler中添加vi.fn()并等待其调用的方法可行,但想寻求更优方案,咨询如何正确等待RTK Query调用完成。
自定义Hook代码:
function useWelcomeMessage() { const { data, error} = useGetWelcomeMessage(); const printMessage = useCallback(async () => { if (data) { alert(data) } if (error) { throw new Error(error); } }, [data, error]); return { printMessage }; } export default useWelcomeMessage;
测试代码(原版本):
const mockMessage = 'hello world'; const server = setupServer( http.get('*/api/v1/getMessage', () => { return HttpResponse.text(mockMessage); }) ); it('calls alert with the data', async () => { const { result } = renderHook(() => useWelcomeMessage(), { wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, }); await act(async () => { await result.current.printMessage(); }); expect(alert).toHaveBeenCalledWith(mockMessage); });
解决方案
1. 暴露RTK Query状态并结合waitFor等待请求完成
RTK Query的useGetWelcomeMessage会返回isSuccess、isLoading等状态,修改Hook暴露这些状态,测试时可以明确等待请求完成:
修改后的自定义Hook:
function useWelcomeMessage() { const { data, error, isSuccess, isLoading} = useGetWelcomeMessage(); const printMessage = useCallback(async () => { if (data) { alert(data) } if (error) { throw new Error(error); } }, [data, error]); return { printMessage, isSuccess, isLoading }; // 暴露请求状态 }
修改后的测试代码:
const mockMessage = 'hello world'; const server = setupServer( http.get('*/api/v1/getMessage', () => { return HttpResponse.text(mockMessage); }) ); it('calls alert with the data', async () => { const { result, waitFor } = renderHook(() => useWelcomeMessage(), { wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, }); // 等待请求成功完成,确保data已就绪 await waitFor(() => expect(result.current.isSuccess).toBe(true)); await act(async () => { await result.current.printMessage(); }); expect(alert).toHaveBeenCalledWith(mockMessage); });
2. 利用RTK Query的refetch方法(手动触发场景)
如果你的useGetWelcomeMessage是手动触发型(未开启自动执行),可以让Hook返回refetch方法,测试时手动触发并等待请求完成:
修改后的自定义Hook:
function useWelcomeMessage() { const { data, error, refetch} = useGetWelcomeMessage(); const printMessage = useCallback(async () => { if (data) { alert(data) } if (error) { throw new Error(error); } }, [data, error]); return { printMessage, refetch }; }
修改后的测试代码:
it('calls alert with the data', async () => { const { result } = renderHook(() => useWelcomeMessage(), { wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, }); // 手动触发请求并等待完成 await act(async () => { await result.current.refetch(); }); await act(async () => { await result.current.printMessage(); }); expect(alert).toHaveBeenCalledWith(mockMessage); });
为什么假定时器无效?
RTK Query的请求基于真实Promise(即使使用MSW模拟),假定时器仅能处理setTimeout这类定时任务,无法模拟Promise的resolve流程,因此不会生效。
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

