如何在React Testing Library中模拟带回调的useLazyFetch自定义Hook
如何直接模拟自定义Hook useLazyFetch,实现调用callFetch时更新res状态
我写了一个自定义Hook useLazyFetch,现在通过模拟axios能完成测试,但想直接模拟这个Hook,实现调用它返回的callFetch回调时,res状态能对应更新,不知道该怎么做。
自定义Hook代码
export const useLazyFetch = ({ method, url, data, config, withAuth = true }: UseFetchArgs): LazyFetchResponse => { const [res, setRes] = useState({ data: null, error: null, loading: false}); const callFetch = useCallback(() => { setRes({ data: null, error: null, loading: true}); const jwtToken = loadItemFromLocalStorage('accessToken'); const authConfig = { headers: { Authorization: `Bearer ${jwtToken}` } }; const combinedConfig = Object.assign(withAuth ? authConfig : {}, config); axios[method](url, data, combinedConfig) .then(res => setRes({ data: res.data, loading: false, error: null})) .catch(error => setRes({ data: null, loading: false, error})) }, [method, url, data, config, withAuth]) return { res, callFetch }; };
当前测试代码
it('does some stuff', async () => { (axios.post as jest.Mock).mockReturnValue({ status: 200, data: { foo: 'bar' } }); const { getByRole, getByText, user } = renderComponent(); user.click(getByRole('button', { name: 'button text' })); await waitFor(() => expect(getByText('success message')).toBeInTheDocument()); });
组件中使用useLazyFetch的示例
const Component = ({ props }: Props) => { const { res, callFetch } = useLazyFetch({ method: 'post', url: `${BASE_URL}/some/endpoint`, data: requestBody }); const { data: postResponse, loading: postLoading, error: postError } = res; return ( <Element header={header} subHeader={subHeader} > <Button disabled={postLoading} onClick={callFetch} > Submit Post Request </Button> </Element> ); }
解决方案
要直接模拟useLazyFetch,核心是让模拟的Hook返回可控的res状态和callFetch函数,调用callFetch时能手动触发状态更新,以下是具体实现步骤:
1. 先模拟Hook的导出
在测试文件中,用Jest mock掉useLazyFetch的导出:
import { useLazyFetch } from './path-to-your-hook'; // 模拟整个Hook模块 jest.mock('./path-to-your-hook', () => ({ useLazyFetch: jest.fn() }));
2. 编写自定义模拟逻辑,实现状态更新
在测试用例里,为useLazyFetch提供自定义的实现——用React的useState维护模拟状态,让callFetch能触发状态变化,和真实Hook行为对齐:
it('测试callFetch触发res状态更新', async () => { // 给mock的Hook注入自定义逻辑 (useLazyFetch as jest.Mock).mockImplementation(() => { const [res, setRes] = useState({ data: null, error: null, loading: false }); const callFetch = () => { // 模拟请求开始的loading状态 setRes({ data: null, error: null, loading: true }); // 模拟异步请求完成,这里可以灵活设置成功/失败状态 setTimeout(() => { setRes({ data: { foo: 'bar' }, error: null, loading: false }); }, 0); }; return { res, callFetch }; }); const { getByRole, getByText, user } = renderComponent(); // 点击按钮触发callFetch await user.click(getByRole('button', { name: 'Submit Post Request' })); // 验证loading状态下按钮禁用 expect(getByRole('button')).toBeDisabled(); // 等待异步状态更新,验证成功信息展示 await waitFor(() => { expect(getByText('success message')).toBeInTheDocument(); expect(getByRole('button')).not.toBeDisabled(); }); });
3. 模拟失败场景(可选)
如果要测试错误分支,只需要修改callFetch里的异步逻辑:
const callFetch = () => { setRes({ data: null, error: null, loading: true }); setTimeout(() => { setRes({ data: null, error: new Error('请求失败'), loading: false }); }, 0); };
注意事项
- 确保测试环境支持React Hooks测试(比如使用
@testing-library/react) - 如果Hook依赖
loadItemFromLocalStorage这类外部函数,若不需要测试该逻辑,可同时mock掉这些函数,避免外部依赖干扰测试结果。
内容的提问来源于stack exchange,提问作者OnlinePseudonym
相关产品推荐
相关产品推荐

