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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 02:12:27