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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 23:10:27