测试包含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
- Incorrect Ref Assignment: You're replacing the entire ref object with
hook.result.current[0] = { current : div }instead of modifying thecurrentproperty of the existing ref. React'suseRefreturns a stable object, so you shouldn't overwrite it. - useLayoutEffect Dependency Issue: Your
useLayoutEffectdepends onref, but therefobject itself never changes (it's created once on mount). When you later setref.current = div, the effect doesn't re-run because the dependency hasn't updated. - ResizeObserver Not Triggered: In test environments, native APIs like
ResizeObserverdon'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 = divinstead of replacing the ref object, which aligns with how React refs work. - Tracking Ref Changes: The
prevElementReftracks whenref.currentupdates, ensuring we recalculate the size when a new element is attached to the ref. - Mocking ResizeObserver: We mock the native API and expose a
triggermethod to manually fire the resize callback, which forces the hook to update the size state.
内容的提问来源于stack exchange,提问作者Iggdrazil
相关产品推荐
相关产品推荐

