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

无法测试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响应解析失败。
修复步骤
  1. 对齐业务触发逻辑:如果需求是页面加载就自动拉取列表,要么在组件的useEffect钩子中调用getCards()触发请求,要么直接替换为挂载即自动请求的useQuery;如果是点击按钮等交互触发拉取,就在测试中先模拟对应交互动作触发请求。
  2. 修正Mock数据结构,给Mock响应添加短延迟,预留出loading状态的渲染时间窗口,避免响应同步返回直接跳过加载态。
  3. 调整测试断言时机,不要在组件创建后同步执行断言,等微任务队列清空、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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 13:45:32