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

测试包含Ref的自定义React Hook:使用renderHook测试useSize Hook时size返回undefined的问题

Fixing useSize Hook Test: Size Remains Undefined

Let's break down why your test isn't working and fix it step by step.

Why Your Current Test Fails

  1. Incorrect Ref Assignment: You're replacing the entire ref object with hook.result.current[0] = { current : div } instead of modifying the current property of the existing ref. React's useRef returns a stable object, so you shouldn't overwrite it.
  2. useLayoutEffect Dependency Issue: Your useLayoutEffect depends on ref, but the ref object itself never changes (it's created once on mount). When you later set ref.current = div, the effect doesn't re-run because the dependency hasn't updated.
  3. ResizeObserver Not Triggered: In test environments, native APIs like ResizeObserver don't run automatically—you need to manually trigger its callback to update the size.

Step 1: Fix the useSize Hook Logic

First, update your hook to detect when ref.current changes and re-calculate the size. We'll use a secondary ref to track the previous element and trigger updates:

import { useState, useRef, useEffect } from 'react';

function useSize<T extends HTMLElement>(): [React.Ref<T>, DOMRect | undefined] {
  const [size, setSize] = useState<DOMRect>();
  const ref = useRef<T | null>(null);
  const prevElementRef = useRef<T | null>(null);

  // Trigger size update when ref.current changes
  useEffect(() => {
    if (ref.current !== prevElementRef.current) {
      prevElementRef.current = ref.current;
      if (ref.current) {
        // Initial size calculation when element is attached
        setSize(ref.current.getBoundingClientRect());
      }
    }
  }, [ref]);

  // Listen for resize changes
  useResizeObserver(ref, (dimensions) => setSize(dimensions.contentRect));

  return [ref, size];
}

Step 2: Correct the Test Code

Now fix your test to properly assign the ref, trigger updates, and simulate the ResizeObserver callback:

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

// Mock ResizeObserver for testing
beforeAll(() => {
  jest.spyOn(window, 'ResizeObserver').mockImplementation((callback) => ({
    observe: jest.fn(),
    unobserve: jest.fn(),
    disconnect: jest.fn(),
    // Expose the callback to trigger manually
    trigger: (rect: DOMRect) => callback([{ contentRect: rect }]),
  }));
});

afterAll(() => {
  jest.restoreAllMocks();
});

test('useSize returns the size of the given element', () => {
  // Create a div with fixed dimensions
  const div = document.createElement('div');
  div.style.width = '200px';
  div.style.height = '100px';
  document.body.appendChild(div);

  // Render the hook
  const hook = renderHook(() => useSize<HTMLDivElement>());

  // Assign the div to the ref (correct way)
  act(() => {
    hook.result.current[0].current = div;
  });

  // Rerender to trigger the useEffect that checks ref changes
  hook.rerender();

  // Manually trigger ResizeObserver callback to update size
  const resizeObserver = (window.ResizeObserver as jest.Mock).mock.instances[0];
  act(() => {
    resizeObserver.trigger(div.getBoundingClientRect());
  });

  // Verify the size is no longer undefined
  expect(hook.result.current[1]).not.toBeUndefined();
  expect(hook.result.current[1]?.width).toBe(200);
  expect(hook.result.current[1]?.height).toBe(100);

  // Clean up
  document.body.removeChild(div);
});

Key Fixes Explained

  • Ref Assignment: We modify hook.result.current[0].current = div instead of replacing the ref object, which aligns with how React refs work.
  • Tracking Ref Changes: The prevElementRef tracks when ref.current updates, ensuring we recalculate the size when a new element is attached to the ref.
  • Mocking ResizeObserver: We mock the native API and expose a trigger method to manually fire the resize callback, which forces the hook to update the size state.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 23:12:40