如何测试自定义容器宽度监听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
相关产品推荐
相关产品推荐

