如何用RTL和Jest测试基于useRef的React组件滚动事件
如何测试React Step组件的滚动逻辑(RTL + Jest)
解决scrollTo is not a function错误
JSDOM默认模拟的DOM元素没有scrollTo方法,导致测试时抛出错误。我们可以全局mock这个方法,避免报错:
beforeAll(() => { // 给所有HTMLElement添加scrollTo的mock实现 HTMLElement.prototype.scrollTo = jest.fn(); }); afterEach(() => { // 每次测试后清空mock调用记录,避免用例间污染 jest.clearAllMocks(); });
滚动逻辑的测试思路
不需要mockuseRef,遵循RTL「行为优先」的测试原则:通过改变组件的currentStep props,验证滚动容器的scrollTo方法是否按预期被调用,以及参数是否正确。
完整测试用例
import { render, screen, act } from '@testing-library/react'; import Step from './Step'; describe('Step Component - Scroll Behavior', () => { const mockSetCurrentStep = jest.fn(); const testSteps = ['第一步', '第二步', '第三步', '第四步']; test('动态滚动开启时,切换currentStep会触发正确位置的滚动', async () => { // 初始渲染currentStep=0的组件 render( <Step steps={testSteps} currentStep={0} setCurrentStep={mockSetCurrentStep} dynamicScroll={true} /> ); // 获取所有步骤按钮和主滚动容器 const stepButtons = screen.getAllByRole('button'); const mainContainer = screen.getByTestId('step-main'); // 切换currentStep为2,重新渲染组件 await act(async () => { render( <Step steps={testSteps} currentStep={2} setCurrentStep={mockSetCurrentStep} dynamicScroll={true} /> ); }); // 计算预期的滚动距离:24*currentStep + 前N个步骤的scrollWidth总和 const expectedScrollLeft = 24 * 2 + stepButtons[0].scrollWidth + stepButtons[1].scrollWidth; // 验证scrollTo是否被正确调用 expect(mainContainer.scrollTo).toHaveBeenCalledWith({ left: expectedScrollLeft, behavior: 'smooth', }); }); test('动态滚动关闭时,切换currentStep不会触发滚动', async () => { render( <Step steps={testSteps} currentStep={0} setCurrentStep={mockSetCurrentStep} dynamicScroll={false} /> ); await act(async () => { render( <Step steps={testSteps} currentStep={2} setCurrentStep={mockSetCurrentStep} dynamicScroll={false} /> ); }); const mainContainer = screen.getByTestId('step-main'); expect(mainContainer.scrollTo).not.toHaveBeenCalled(); }); test('currentStep超出steps长度时,不会触发滚动', async () => { render( <Step steps={testSteps} currentStep={0} setCurrentStep={mockSetCurrentStep} dynamicScroll={true} /> ); // currentStep=4超出steps的索引范围(0-3) await act(async () => { render( <Step steps={testSteps} currentStep={4} setCurrentStep={mockSetCurrentStep} dynamicScroll={true} /> ); }); const mainContainer = screen.getByTestId('step-main'); expect(mainContainer.scrollTo).not.toHaveBeenCalled(); }); });
关键说明
- 避免mock useRef:RTL鼓励测试组件的外部行为而非内部实现,直接操作props并验证DOM方法的调用更符合测试原则,也能避免依赖组件内部实现细节导致的测试脆弱。
- 真实计算滚动距离:通过获取DOM元素的
scrollWidth来计算预期值,和组件内部的逻辑保持一致,保证测试的准确性。 - act的使用:重新渲染组件时用
act包裹,确保React的状态更新和副作用都完成后再进行断言,避免测试不稳定。
内容的提问来源于stack exchange,提问作者atadnmz
相关产品推荐
相关产品推荐

