无法测试Apollo GraphQL查询loading状态的解决方案
问题背景
实现了基于Apollo Client的GraphQL列表拉取逻辑:请求未返回时页面全屏展示...Loading加载提示,编写对应单元测试时始终无法通过,校验loading状态下的加载文本时,实际拿到的是加载完成后的渲染内容,判断loading状态未被正确触发。
原有实现代码
GraphQL查询逻辑
const [getCards, { data: cardsData, error: cardsError, loading: cardsLoading }] = useLazyQuery( GET_LIST, { fetchPolicy: 'no-cache', } );
加载状态渲染逻辑
if (cardsLoading) { return ( <View style={{ width: '100%', height: '100%', backgroundColor: 'white', alignItems: 'center', justifyContent: 'center', }} > <Text>...Loading</Text> </View> ); }
原有失败的测试用例
const getCardsMock = [ { request: { query: GET_LIST, }, result: { cards: { id: 0, name: 'card', }, } }, ]; it('Loading state test', async () => { const component = renderer.create( <MockedProvider mocks={getCardsMock} addTypename={false}> <HomeViewApollo /> </MockedProvider> ); const tree = component.toJSON(); expect(tree.children).toContain('...Loading'); });
失败原因
测试不通过是三个问题共同导致的:
useLazyQuery默认不会在组件挂载时自动发起请求,只有手动调用返回的getCards方法才会触发请求,初始渲染时cardsLoading值为false,根本不会进入加载态的渲染分支。- React和Apollo的状态更新是异步执行的,创建组件后同步获取渲染结果时,loading状态还没完成更新,且Mock请求默认会同步返回,组件会直接从初始状态跳到加载完成状态,完全跳过中间的loading渲染阶段。
- 写的Mock数据结构不符合
MockedProvider的规范,响应结果必须包裹在data字段下,原代码直接将cards放在result根层级,会导致Mock响应解析失败。
修复步骤
- 对齐业务触发逻辑:如果需求是页面加载就自动拉取列表,要么在组件的
useEffect钩子中调用getCards()触发请求,要么直接替换为挂载即自动请求的useQuery;如果是点击按钮等交互触发拉取,就在测试中先模拟对应交互动作触发请求。 - 修正Mock数据结构,给Mock响应添加短延迟,预留出loading状态的渲染时间窗口,避免响应同步返回直接跳过加载态。
- 调整测试断言时机,不要在组件创建后同步执行断言,等微任务队列清空、Apollo将loading状态更新到组件后,再校验加载文本是否存在。
修复后的测试用例参考
const getCardsMock = [ { request: { query: GET_LIST, }, // 添加100ms延迟,预留loading态渲染窗口 delay: 100, result: { // 补全Mock要求的data层级 data: { cards: { id: 0, name: 'card', }, } } }, ]; it('Loading state test', async () => { let component; act(() => { component = renderer.create( <MockedProvider mocks={getCardsMock} addTypename={false}> <HomeViewApollo /> </MockedProvider> ); }); // 等待一个事件循环,让Apollo完成请求触发、loading状态更新 await act(async () => { await Promise.resolve(); }); const loadingRenderTree = component.toJSON(); expect(loadingRenderTree.children).toContain('...Loading'); // 等待Mock响应返回,组件更新为加载完成状态,可继续编写后续断言 await act(async () => { await new Promise(resolve => setTimeout(resolve, 150)); }); // const loadedRenderTree = component.toJSON(); // 此处编写加载完成后的内容断言即可 });
如果你使用React Testing Library编写测试,不需要手动包裹
act,直接使用waitFor或者findBy*系列异步查询方法等待状态更新即可,核心逻辑一致:不要在组件刚挂载的同步时机断言异步状态。
内容的提问来源于stack exchange,提问作者Stas Motorny
相关产品推荐
相关产品推荐

