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

React Testing Library mock offsetWidth/Left 修复resize测试用例问题

现有测试的问题点
  • 执行顺序完全颠倒:resize方法只有在isResizing为true时才会执行宽度计算,现有代码先调用resize再触发enableResize,第一次调用resize时状态还是未拖拽,根本不会走计算逻辑。
  • DOM属性mock时机错误:offsetWidth、offsetLeft是resize执行时实时读取的,现有代码在第一次调用resize之后才mock这两个值,mock完全不生效。
  • 事件触发完全不对:Hook内部监听的是document上的mousemove事件触发resize,和window的resize事件没有任何关系,window.dispatchEvent(new Event('resize'))这行代码完全不会触发宽度计算。
  • 手动调用setWidth属于无效测试:核心逻辑是拖拽时自动计算宽度,手动调用setWidth塞值完全绕过了resize的计算逻辑,等于没测。
  • 没有做mock还原:直接修改document.body的原生属性,会污染全局测试环境,导致其他用例随机失败。
  • mock写法错误:offsetWidth是DOM元素的只读属性,直接defineProperty只传value的话默认不可写,赋值会静默失败。
可行测试方案

按照真实用户拖拽路径设计用例,提前mock DOM属性并做好还原,通过真实事件派发触发逻辑,不要手动篡改Hook内部状态。

完整测试代码示例:

import { renderHook, act } from '@testing-library/react';
import useResize from './useResize';

// 测试默认参数
const defaultProps = {
  minWidth: 300,
  maxWidth: 1200,
};

// 保存DOM原生属性,测试后还原避免环境污染
const originalOffsetWidth = Object.getOwnPropertyDescriptor(
  HTMLElement.prototype,
  'offsetWidth'
);
const originalOffsetLeft = Object.getOwnPropertyDescriptor(
  HTMLElement.prototype,
  'offsetLeft'
);

describe('useResize 宽度拖拽Hook', () => {
  // 每个用例执行前重置DOM属性mock
  beforeEach(() => {
    Object.defineProperty(document.body, 'offsetWidth', {
      writable: true,
      configurable: true,
      value: 1536, // 模拟页面可视区宽度1536px
    });
    Object.defineProperty(document.body, 'offsetLeft', {
      writable: true,
      configurable: true,
      value: 0,
    });
  });

  // 每个用例执行后还原原生属性
  afterEach(() => {
    if (originalOffsetWidth) {
      Object.defineProperty(document.body, 'offsetWidth', originalOffsetWidth);
    }
    if (originalOffsetLeft) {
      Object.defineProperty(document.body, 'offsetLeft', originalOffsetLeft);
    }
  });

  it('拖拽过程中应该根据鼠标位置正确计算更新宽度', () => {
    const { result } = renderHook(() => useResize(defaultProps));
    
    // 模拟鼠标按下拖拽条,进入调整状态
    act(() => {
      result.current.enableResize();
    });
    expect(result.current.isResizing).toBe(true);

    // 模拟鼠标移动到clientX=1000位置,派发mousemove事件触发计算
    const mouseMoveEvent = new MouseEvent('mousemove', {
      clientX: 1000,
      bubbles: true,
      cancelable: true,
    });
    act(() => {
      document.dispatchEvent(mouseMoveEvent);
    });

    // 计算预期值:1536 - (1000 - 0) = 536,在300~1200区间内,应该更新
    expect(result.current.width).toBe(536);
  });

  it('计算宽度小于最小宽度时不更新', () => {
    const { result } = renderHook(() => useResize(defaultProps));
    act(() => {
      result.current.enableResize();
    });

    // 模拟鼠标移动到最右侧,计算宽度=1536-1300=236 < 300,保持最小宽度
    const mouseMoveEvent = new MouseEvent('mousemove', { clientX: 1300 });
    act(() => {
      document.dispatchEvent(mouseMoveEvent);
    });
    expect(result.current.width).toBe(defaultProps.minWidth);
  });

  it('计算宽度大于最大宽度时不更新', () => {
    const { result } = renderHook(() => useResize(defaultProps));
    act(() => {
      result.current.enableResize();
    });

    // 模拟鼠标移动到最左侧,计算宽度=1536-200=1336 > 1200,保持最小宽度
    const mouseMoveEvent = new MouseEvent('mousemove', { clientX: 200 });
    act(() => {
      document.dispatchEvent(mouseMoveEvent);
    });
    expect(result.current.width).toBe(defaultProps.minWidth);
  });

  it('鼠标松开后退出拖拽状态,不再响应鼠标移动更新宽度', () => {
    const { result } = renderHook(() => useResize(defaultProps));
    act(() => {
      result.current.enableResize();
    });

    // 模拟鼠标松开
    act(() => {
      document.dispatchEvent(new MouseEvent('mouseup'));
    });
    expect(result.current.isResizing).toBe(false);

    // 再次移动鼠标,宽度不应该变化
    const mouseMoveEvent = new MouseEvent('mousemove', { clientX: 1000 });
    act(() => {
      document.dispatchEvent(mouseMoveEvent);
    });
    expect(result.current.width).toBe(defaultProps.minWidth);
  });
});
关键注意点
  • mock DOM只读属性时必须配置writable: true和configurable: true,否则原生属性无法修改,mock会静默失败。
  • 不要手动调用Hook返回的resize方法测试,要通过派发真实事件触发逻辑,才能覆盖事件绑定、解绑、依赖更新等边界场景。
  • 所有修改全局对象(document、window)的操作必须在测试结束后还原,避免污染其他用例的执行环境。
  • 测试流程要和真实用户操作路径完全一致:按下拖拽条→移动鼠标→松开鼠标,不要跳步或者手动调用内部方法篡改状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 01:57:22