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

如何测试自定义容器宽度监听Hook?

问题:如何测试基于容器宽度的响应式嵌入组件?

我开发了一个可嵌入Web应用的组件,未使用IFrame,因此直接运行在宿主应用的window对象中。该组件的响应式逻辑不依赖宿主窗口尺寸,而是根据自身容器宽度应用不同的Tailwind margin类,因此我用自定义HookuseContainerWidth实现响应式(而非媒体查询)。组件和Hook功能正常,但不清楚如何进行测试,求指导。

组件与Hook代码

import { useContainerWidth } from 'custom-hooks'

function EmbeddedComponent() {
  const { targetRef, containerWidth } = useContainerWidth()
 
  return (
       <div
         data-testid="test-embeddedComponent"
         className={clsx(
            containerWidth < 640 && 'my-0',
            containerWidth >= 640 && containerWidth < 768 && 'my-8',
            containerWidth >= 768 && containerWidth < 1024 && 'my-16', // 修复拼写错误:conatinerWidth → containerWidth
            containerWidth >= 1024 && 'my-20'
         )}
         ref={targetRef} {/* 补充:给容器绑定ref,这是Hook生效的核心 */}
       >
         <LayoutComponent containerWidth={containerWidth} />
       </div>
  )
}

function LayoutComponent({ containerWidth }: { containerWidth: number }) {
  return (
       <div>
           <div
             className={clsx(
                containerWidth < 640 && 'mb-2',
                containerWidth >= 640 && containerWidth < 768 && 'mb-7',
                containerWidth >= 768 && containerWidth < 1024 && 'mb-12', // 修复拼写错误
                containerWidth >= 1024 && 'mb-14'
             )}
             data-testid="top-component-wrapper"
           >
              <TopComponent/>
           </div>
           <div>
              <BottomComponent/>
           </div>
       </div>
}


export function useContainerWidth() {
   const [containerWidth, setContainerWidth] = React.useState(400)
   const targetRef = React.useRef<HTMLDivElement>(null)

   React.useEffect(() => {
      if (window === undefined) return

      const getContainerWidth = () => {
          if (targetRef.current) {
             setContainerWidth(targetRef.current.offsetWidth)
          }
      }

      getContainerWidth()
      window.addEventListener('resize', getContainerWidth)

      return () => {
         window.removeEventListener('resize', getContainerWidth)
      }
   }, []) // 修复依赖问题:移除containerWidth,避免无限循环

   return {
      containerWidth,
      targetRef
   }
}

我的测试尝试(存在困惑)

it('should apply the right margin classes on resize', () => {

    ??? How do I test this ???
    ??? what do I do with targetRef ???

    const { result } = renderHook(() => useContainerWidth())
    jest.spyOn(window, ???).mockReturnValue(650)
    render(<EmbeddedComponent />)
    expect(screen.getByTestId('test-embeddedComponent')).toHaveClass('my-8')

    ??? Can I then set window size to something else like 800 ???
    expect(screen.getByTestId('test-embeddedComponent')).toHaveClass('my-16')
  }
)


// I've also tried this one

it('should apply the right margins for a window size', async () => {
    const { result } = renderHook(() => useContainerWidth())

    act(() => {
      window.resizeTo(650, 650)
    })

    await mockDelay(300)

    expect(result.current.containerWidth).toBe(650)
    expect(screen.getByTestId('test-embeddedComponent')).toHaveClass('my-8')
  })

解决方案:针对性测试容器宽度变化

核心思路

你的组件依赖容器自身宽度而非窗口宽度,所以测试重点是模拟容器的宽度变化,而非修改window尺寸。另外先修复代码中的拼写错误、Hook依赖问题,以及漏绑ref的问题,否则测试无法正常运行。

1. 测试自定义Hook useContainerWidth

用renderHook模拟DOM元素的宽度变化,触发resize事件验证状态更新:

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

describe('useContainerWidth', () => {
  it('updates containerWidth when target element resizes', () => {
    // 模拟DOM元素
    const mockContainer = { offsetWidth: 400 } as HTMLDivElement;
    const { result } = renderHook(() => useContainerWidth());

    // 给ref赋值
    act(() => {
      result.current.targetRef.current = mockContainer;
    });

    // 触发resize,验证初始宽度
    act(() => {
      window.dispatchEvent(new Event('resize'));
    });
    expect(result.current.containerWidth).toBe(400);

    // 修改容器宽度,再次触发resize验证更新
    act(() => {
      mockContainer.offsetWidth = 650;
      window.dispatchEvent(new Event('resize'));
    });
    expect(result.current.containerWidth).toBe(650);
  });
});

2. 测试EmbeddedComponent的类名切换

模拟容器的offsetWidth变化,触发resize后检查对应的Tailwind类:

import { render, screen, act } from '@testing-library/react';
import EmbeddedComponent from './EmbeddedComponent';

describe('EmbeddedComponent', () => {
  // 封装设置容器宽度并触发更新的工具函数
  const updateContainerWidth = (width: number) => {
    const component = screen.getByTestId('test-embeddedComponent');
    // 重写offsetWidth属性(原生DOM属性只读,需用defineProperty)
    Object.defineProperty(component, 'offsetWidth', { value: width });
    // 触发resize事件让Hook更新状态
    act(() => {
      window.dispatchEvent(new Event('resize'));
    });
  };

  it('applies correct margin classes based on container width', () => {
    render(<EmbeddedComponent />);
    const component = screen.getByTestId('test-embeddedComponent');

    // 测试宽度<640的情况
    updateContainerWidth(500);
    expect(component).toHaveClass('my-0');

    // 测试640<=宽度<768的情况
    updateContainerWidth(650);
    expect(component).toHaveClass('my-8');

    // 测试768<=宽度<1024的情况
    updateContainerWidth(800);
    expect(component).toHaveClass('my-16');

    // 测试宽度>=1024的情况
    updateContainerWidth(1200);
    expect(component).toHaveClass('my-20');
  });
});

3. 测试LayoutComponent

直接传入不同的containerWidth属性,验证子元素的类名:

import { render, screen } from '@testing-library/react';
import LayoutComponent from './LayoutComponent';

describe('LayoutComponent', () => {
  it('applies correct bottom margin to TopComponent wrapper', () => {
    // 宽度<640
    render(<LayoutComponent containerWidth={500} />);
    const topWrapper = screen.getByTestId('top-component-wrapper');
    expect(topWrapper).toHaveClass('mb-2');

    // 640<=宽度<768
    render(<LayoutComponent containerWidth={650} />);
    expect(topWrapper).toHaveClass('mb-7');

    // 768<=宽度<1024
    render(<LayoutComponent containerWidth={800} />);
    expect(topWrapper).toHaveClass('mb-12');

    // 宽度>=1024
    render(<LayoutComponent containerWidth={1200} />);
    expect(topWrapper).toHaveClass('mb-14');
  });
});

关键注意事项

  • 不要用window.resizeTo:你的组件逻辑不依赖窗口宽度,修改窗口尺寸不会影响容器宽度
  • 用Object.defineProperty模拟offsetWidth:原生DOM元素的offsetWidth是只读属性,直接赋值无效
  • 用act包裹状态更新操作:确保React状态同步,避免测试报错
  • 先修复代码bug:拼写错误、Hook依赖、漏绑ref是测试通过的前提

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 11:40:00