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

测试使用fetch的Composable时返回undefined结果的解决方法

问题描述

我有一个使用fetch获取数据的Composable,代码如下:

export const useFetchData = (q?: string) => {
  const data: Ref<Data | undefined> = ref();
  const error: Ref<Error | undefined> = ref();
  const isLoading = ref(true);

  const fetchData = async (q?: string) => {
    if (!q)
      return
    isLoading.value = true;
    fetch(`https://url/q=${q}`)
      .then(r => r.json().then(d => {
        data.value = d
      }))
      .catch(err => error.value = err)
      .finally(() => {isLoading.value = false});
  }

  fetchData(location, langCode);

  return { data, error, isLoading };
}

我使用以下代码进行测试:

const testData = {data: 'value'};

global.fetch = vi.fn(() => Promise.resolve({
        json: () => Promise.resolve(testData),
        ok: true,
      })) as any;

const { data, error, isLoading } = useFetchData('test');

expect(error.value).toBeUndefined;
expect(isLoading.value).toBeFalsy;
expect(data.value).toBe(testData) //failed!;

最后一个断言失败,因为data.value为undefined,但相同代码在组件中运行正常。请问如何让测试正常通过?

解决方案

导致测试失败有两个核心问题,逐个解决即可:

1. 修复Composable的传参错误

你的useFetchData函数接收了q参数,但内部调用fetchData(location, langCode)时,没有传入这个q,反而用了未定义的location和langCode变量,这会导致fetchData里的if (!q)判断成立,直接返回,根本没执行fetch请求。

修改Composable里的调用代码:

// 把 fetchData(location, langCode); 改成
fetchData(q);

2. 处理测试中的异步操作

fetch是异步操作,测试代码中直接调用useFetchData后立即断言,此时Promise还未完成,data.value还没被赋值。需要等待异步操作完成后再做断言:

方法一:使用flushPromises(Vue测试工具提供)

如果是Vue项目的测试环境,@vue/test-utils提供了flushPromises方法,可以批量处理所有待解决的Promise:

import { flushPromises } from '@vue/test-utils';

test('useFetchData works correctly', async () => {
  const testData = {data: 'value'};

  global.fetch = vi.fn(() => Promise.resolve({
          json: () => Promise.resolve(testData),
          ok: true,
        })) as any;

  const { data, error, isLoading } = useFetchData('test');

  // 初始状态断言
  expect(isLoading.value).toBeTruthy();
  expect(data.value).toBeUndefined();

  // 等待所有异步操作完成
  await flushPromises();

  // 最终状态断言
  expect(error.value).toBeUndefined();
  expect(isLoading.value).toBeFalsy();
  expect(data.value).toEqual(testData); // 建议用toEqual而非toBe,因为是对象比较
});

方法二:直接等待fetch的Promise

如果没有flushPromises,也可以手动等待mock的fetch返回的Promise:

test('useFetchData works correctly', async () => {
  const testData = {data: 'value'};
  const fetchPromise = Promise.resolve({
    json: () => Promise.resolve(testData),
    ok: true,
  });
  global.fetch = vi.fn(() => fetchPromise) as any;

  const { data, error, isLoading } = useFetchData('test');

  await fetchPromise;
  // 还要等待json()的Promise完成
  await Promise.resolve();

  expect(error.value).toBeUndefined();
  expect(isLoading.value).toBeFalsy();
  expect(data.value).toEqual(testData);
});

额外优化建议

  • 建议把fetchData里的嵌套then改成async/await写法,避免回调地狱,代码更易读:
const fetchData = async (q?: string) => {
  if (!q) return;
  isLoading.value = true;
  try {
    const res = await fetch(`https://url/q=${q}`);
    const d = await res.json();
    data.value = d;
  } catch (err) {
    error.value = err as Error;
  } finally {
    isLoading.value = false;
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:33:10