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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 23:12:27