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

如何为Jest/Vitest测试传递可生成FormData的FormEvent?

测试React表单提交函数时模拟FormEvent的两种方法

针对你要测试的handleSave函数,下面提供两种传递可用FormEvent的方案,分别适合不同的测试场景:

一、Mock FormEvent(推荐用于单元测试)

单元测试不需要依赖真实DOM,只需模拟函数用到的preventDefault和currentTarget即可。核心是让e.currentTarget能被FormData正确解析出所需字段。

实现代码

import { describe, it, expect, vi } from 'vitest';
import { handleSave } from './your-module-path';
import axios from 'axios';
import { DEVELOPER_ACTION_TYPES } from './your-action-types-path';

// Mock axios 避免发起真实请求
vi.mock('axios');

describe('handleSave', () => {
  it('submits form data successfully', async () => {
    // Mock 辅助函数
    const mockDispatch = vi.fn();
    const mockHandleClose = vi.fn();
    const mockSetErrorMessage = vi.fn();

    // 模拟表单元素,让FormData能读取字段
    const mockForm = {
      elements: {
        id: { value: '123' },
        name: { value: 'Alice' },
        // 添加你需要的其他表单字段
      },
      // 实现迭代器,让FormData可以遍历表单控件
      [Symbol.iterator]: function* () {
        for (const key in this.elements) {
          yield { name: key, value: this.elements[key].value };
        }
      }
    } as unknown as HTMLFormElement;

    // 构造Mock的FormEvent
    const mockEvent = {
      preventDefault: vi.fn(),
      currentTarget: mockForm
    } as unknown as React.FormEvent<HTMLFormElement>;

    // 模拟axios成功响应
    (axios as vi.Mock).mockResolvedValue({ data: { id: '123', name: 'Alice' } });

    // 执行测试函数
    await handleSave(mockDispatch, mockEvent, mockHandleClose, mockSetErrorMessage);

    // 断言验证
    expect(mockEvent.preventDefault).toHaveBeenCalled();
    expect(axios).toHaveBeenCalledWith(expect.objectContaining({
      method: 'PUT',
      url: expect.stringContaining('/developers/123'),
      data: expect.objectContaining({ id: '123', name: 'Alice' })
    }));
    expect(mockDispatch).toHaveBeenCalledWith({
      type: DEVELOPER_ACTION_TYPES.EDIT,
      payload: { id: '123', name: 'Alice' }
    });
    expect(mockHandleClose).toHaveBeenCalled();
    expect(mockSetErrorMessage).not.toHaveBeenCalled();
  });
});

优势

  • 完全脱离DOM环境,测试速度快
  • 可以精准控制每个字段的值,便于覆盖多种测试场景
  • 无需维护真实DOM元素,代码更简洁

二、创建真实FormEvent实例(适合集成测试)

如果需要验证FormData对真实表单的解析逻辑,可以创建真实的DOM表单和FormEvent实例,模拟浏览器环境下的提交场景。

实现代码

import { describe, it, expect, vi } from 'vitest';
import { handleSave } from './your-module-path';
import axios from 'axios';
import { DEVELOPER_ACTION_TYPES } from './your-action-types-path';

vi.mock('axios');

describe('handleSave', () => {
  it('handles real form submission correctly', async () => {
    const mockDispatch = vi.fn();
    const mockHandleClose = vi.fn();
    const mockSetErrorMessage = vi.fn();

    // 创建真实表单元素
    const form = document.createElement('form');
    // 添加表单控件
    const idInput = document.createElement('input');
    idInput.name = 'id';
    idInput.value = '456';
    form.appendChild(idInput);

    const nameInput = document.createElement('input');
    nameInput.name = 'name';
    nameInput.value = 'Bob';
    form.appendChild(nameInput);

    // 创建真实FormEvent,并手动设置currentTarget
    const event = new FormEvent('submit', { cancelable: true });
    Object.defineProperty(event, 'currentTarget', { value: form });

    // 模拟axios成功响应
    (axios as vi.Mock).mockResolvedValue({ data: { id: '456', name: 'Bob' } });

    await handleSave(mockDispatch, event, mockHandleClose, mockSetErrorMessage);

    // 断言验证
    expect(event.preventDefault).toHaveBeenCalled();
    expect(axios).toHaveBeenCalledWith(expect.objectContaining({
      url: expect.stringContaining('/developers/456'),
      data: expect.objectContaining({ id: '456', name: 'Bob' })
    }));
    expect(mockDispatch).toHaveBeenCalled();
    expect(mockHandleClose).toHaveBeenCalled();
  });
});

注意事项

  • 需确保测试环境支持DOM(vitest默认集成jsdom,无需额外配置)
  • 测试速度略慢于Mock方式,但更贴近真实用户场景

总结

  • 日常单元测试优先用Mock方案,高效且易维护
  • 若需验证FormData解析逻辑或表单字段联动,再用真实FormEvent方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 10:55:06