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

React中用Jest测试fetch调用时FormData为何显示为空对象?

问题:Jest测试包含FormData的fetch调用时,body显示为空对象

业务代码

export const submitform = async (url:string, file:File, params:{}) => {
  const body = new FormData()
  body.append('file', file)

  if (Object.keys(params).length > 0) {
    Object.keys(params).forEach(key => {
      body.append(key, params[key])
    })
  }

  const res = await fetch(url, {
    method: 'post',
    headers: {
      authorization: `Bearer ${'abc'}`
    },
    body
  })
  return res.json()
}

测试代码

const myParams = { a: 'a', b: 'b' }
const response = await submitform(postUrl, file, myParams)
expect(fetch).toHaveBeenCalledTimes(1)

expect(fetch).toHaveBeenCalledWith(
  "/url",
  expect.objectContaining({
    method: 'post',
    headers: {
      authorization: `Bearer ${'abc'}`
    },
    body: myParams
  })
)

报错信息

Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

Expected: "/url", ObjectContaining {"body": {"a": "a", "b": "b"}, "headers": {"authorization": "Bearer abc"}, "method": "post"}
Received: "/url", {"body": {}, "headers": {"authorization": "Bearer abc"}, "method": "post"}

Number of calls: 1

日志打印结果

BODY FormData {}
FILE File {}
A a
B b

疑问

明明日志能获取到FormData中的数据,为什么fetch调用中的body会显示为空对象?


原因与解决方案

核心原因

  1. FormData的特殊性:FormData是浏览器原生的特殊对象,Jest在打印或断言时无法直接将其序列化为普通对象,所以会显示为{},但内部数据实际存在。
  2. 断言逻辑错误:测试代码里直接断言body等于普通对象myParams,但业务代码传入的是FormData实例,两者类型不匹配,必然断言失败。

解决方案

要正确测试FormData,需手动验证其内部内容,而非直接断言等于普通对象,以下是两种可行方式:

方式1:封装辅助函数验证FormData

// 辅助函数:校验FormData的内容
const validateFormData = (formData: FormData, expectedParams: Record<string, string>, expectedFile: File) => {
  // 验证普通参数
  Object.entries(expectedParams).forEach(([key, value]) => {
    expect(formData.get(key)).toBe(value)
  })
  // 验证文件
  expect(formData.get('file')).toBe(expectedFile)
}

// 修改测试代码
const myParams = { a: 'a', b: 'b' }
await submitform(postUrl, file, myParams)
expect(fetch).toHaveBeenCalledTimes(1)

// 获取fetch的调用参数
const [callUrl, callOptions] = fetch.mock.calls[0]
// 验证URL和请求配置
expect(callUrl).toBe("/url")
expect(callOptions).toMatchObject({
  method: 'post',
  headers: { authorization: 'Bearer abc' }
})

// 验证FormData内容
validateFormData(callOptions.body as FormData, myParams, file)

方式2:直接捕获FormData并逐个验证

// 提前mock fetch
global.fetch = jest.fn().mockResolvedValue({
  json: jest.fn().mockResolvedValue({ success: true })
})

// 执行测试
const myParams = { a: 'a', b: 'b' }
await submitform(postUrl, file, myParams)

// 获取调用时的FormData
const [, options] = fetch.mock.calls[0]
const formData = options.body as FormData

// 逐个验证内容
expect(formData.get('file')).toBe(file)
expect(formData.get('a')).toBe('a')
expect(formData.get('b')).toBe('b')

补充说明

日志中打印FormData {}是正常现象,因为FormData的默认toString()不会返回内部数据。如果需要查看FormData内容,可手动遍历:

// 正确打印FormData内容的方式
for (const [key, value] of formData.entries()) {
  console.log(`${key}: ${value}`)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 00:45:13