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'); });
问题原因
- 多个独立
waitFor导致的异步时序问题:两个waitFor分开执行时,第一个waitFor在data更新后就会resolve,但此时loadingState的更新可能因为React的状态批量更新机制,还没被测试框架捕获到,第二个waitFor会继续等待,引发不必要的重渲染循环。 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
相关产品推荐
相关产品推荐

