测试使用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
相关产品推荐
相关产品推荐

