使用Vitest测试FormData提交Hook时,request.formData为undefined的问题
问题分析与解决方案
我有一个使用FormData提交数据的Hook,代码如下:
export function useTrackIngredientsViewed( productType: IngredientsViewedProductType ) { return (product: ShopifyPick) => { const formData = new FormData() formData.set('product', JSON.stringify(product)) formData.set('type', productType) fetch('/actions/events/track-ingredients-viewed', { method: 'post', body: formData, }) } }
这个Hook在生产环境正常工作,但用Vitest测试时遇到了问题:尝试通过Object.fromEntries(await request.formData())获取表单数据时,req.formData是undefined。另外还观察到:
req.body是ReadableStream- 调用
req.json()会因意外字符报错 await req.text()返回[object FormData]
原因
在浏览器环境中,fetch会自动将FormData序列化为multipart/form-data格式的请求体,但Vitest依赖的jsdom环境对fetch的模拟存在局限性——它没有正确处理FormData的序列化逻辑,而是直接将FormData对象转为字符串[object FormData],导致mock请求的req对象无法提供formData()解析方法,也无法用常规方式解析请求体。
解决方案
不需要在mock请求中解析请求体,直接断言Hook传入fetch的FormData对象内容即可,这是更直接且可靠的测试方式:
测试示例(Vitest)
import { useTrackIngredientsViewed } from './path-to-hook'; import { vi, expect, test } from 'vitest'; test('正确提交食材浏览事件的FormData', () => { // 模拟fetch const mockFetch = vi.spyOn(global, 'fetch').mockResolvedValueOnce({ ok: true }); // 初始化Hook并调用 const trackFn = useTrackIngredientsViewed('snack'); const testProduct = { id: 123, title: '测试零食' }; trackFn(testProduct); // 获取fetch的调用参数 const [requestUrl, requestOptions] = mockFetch.mock.calls[0]; // 验证请求基本信息 expect(requestUrl).toBe('/actions/events/track-ingredients-viewed'); expect(requestOptions.method).toBe('post'); // 直接验证FormData内容 const submittedFormData = requestOptions.body as FormData; expect(submittedFormData.get('type')).toBe('snack'); expect(JSON.parse(submittedFormData.get('product') as string)).toEqual(testProduct); // 清理模拟 mockFetch.mockRestore(); });
如果用MSW模拟后端接口,同样可以直接从req.body获取原始FormData对象进行断言:
import { rest } from 'msw'; rest.post('/actions/events/track-ingredients-viewed', async (req, res, ctx) => { const formData = req.body as FormData; const product = JSON.parse(formData.get('product') as string); const type = formData.get('type') as string; // 执行断言逻辑 expect(product.id).toBe(123); expect(type).toBe('snack'); return res(ctx.status(200)); });
内容的提问来源于stack exchange,提问作者Jonathan Tuzman
相关产品推荐
相关产品推荐

