You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 08:35:24