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

React Hook异步状态测试异常:loadingState断言无法通过

React Hook单元测试中loadingState断言失败问题排查与解决

问题场景

我有一个维护loadingState和data状态的React Hook,初始加载时会请求接口并更新状态,浏览器中运行正常,但用Vitest + @testing-library/react测试时,data的断言能通过,loadingState的断言始终失败,且出现大量重渲染。

Hook代码:

export const usePeopleListAPIService = ({requestQueryParams}) => {
    const apiClient = useApiClient();
    const [loadingState, setLoadingState] = React.useState<PeopleListLoadingState>('initial');
    const [data, setData] = React.useState<PeopleListData>({
        totalCount: 0,
        pageCount: -1,
        rows: [],
    });

    React.useEffect(() => {
        setLoadingState('loading');
        (async () => {
            const response = await apiClient.GET('/api/people', {
                params: {
                    query: requestQueryParams,
                },
            });

            handleResponse(response, {               
                successHandler: (data) => {
                    setLoadingState('success');
                    setData({
                        pageCount: data.pageMetadata?.totalPages ?? 0,
                        totalCount: data.pageMetadata?.totalElements ?? 0,
                        rows: data?.items ?? [],
                    });
                },
                errorHandler: () => {
                    setLoadingState('error');
                },
            });
            return;
        })();
    }, [requestQueryParams]);

    return {
        loadingState,
        data,
    };
}

测试代码:

const { result } = renderHook(
    () => usePeopleListAPIService({ requestQueryParams}),
    {
        wrapper: ProvidersWrapper,
    }
);

await waitFor(() => {
    expect(result.current.data).toEqual({
        totalCount: peopleEndpointResponse.pageMetadata.totalElements,
        pageCount: peopleEndpointResponse.pageMetadata.totalPages,
        rows: peopleEndpointResponse.items,
    });
});

await waitFor(() => {
    expect(result.current.loadingState).toEqual('success');
});

问题原因

  1. 多个独立waitFor导致的异步时序问题:两个waitFor分开执行时,第一个waitFor在data更新后就会resolve,但此时loadingState的更新可能因为React的状态批量更新机制,还没被测试框架捕获到,第二个waitFor会继续等待,引发不必要的重渲染循环。
  2. result.current的快照特性:result.current是Hook当前状态的快照,当Hook多次更新时,分开的waitFor可能在第一个断言后,Hook已经完成所有状态更新,但第二个waitFor还在等待一个已经发生的状态变化,导致超时或断言失败。

解决方案

方案1:合并断言到同一个waitFor中

将两个断言放在同一个waitFor回调里,确保等待所有状态更新完成后再一起断言:

const { result } = renderHook(
    () => usePeopleListAPIService({ requestQueryParams}),
    {
        wrapper: ProvidersWrapper,
    }
);

await waitFor(() => {
    expect(result.current.data).toEqual({
        totalCount: peopleEndpointResponse.pageMetadata.totalElements,
        pageCount: peopleEndpointResponse.pageMetadata.totalPages,
        rows: peopleEndpointResponse.items,
    });
    expect(result.current.loadingState).toEqual('success');
});

方案2:检查handleResponse的同步性

确认handleResponse是同步执行的,如果它内部包含异步逻辑(比如额外的Promise),会导致setLoadingState('success')延迟执行,此时需要确保测试等待所有异步操作完成。如果handleResponse是异步的,需要在测试中等待其完成,或者修改Hook逻辑确保状态更新是同步触发的。

方案3:验证MSW Mock的响应类型

确保MSW的mock返回的是会触发successHandler的响应,比如正确的状态码(200系列),避免因为响应被错误归类到errorHandler导致loadingState被设置为error而非success。

方案4:显式用act包裹异步逻辑(可选)

虽然@testing-library/react的renderHook和waitFor已经内置了act处理,但如果问题依然存在,可以尝试显式包裹:

const { result } = renderHook(
    () => usePeopleListAPIService({ requestQueryParams}),
    {
        wrapper: ProvidersWrapper,
    }
);

await act(async () => {
    await waitFor(() => {
        expect(result.current.loadingState).toEqual('success');
        expect(result.current.data).toEqual({
            totalCount: peopleEndpointResponse.pageMetadata.totalElements,
            pageCount: peopleEndpointResponse.pageMetadata.totalPages,
            rows: peopleEndpointResponse.items,
        });
    });
});

内容的提问来源于stack exchange,提问作者eliasondrej

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 16:42:13