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

如何在RTL和Jest中测试scrollIntoView?为何无点击测试仍通过

问题

我正在测试scrollIntoView事件,页面中的“Explore Cars”按钮点击后会滚动到discover区域。我采用以下方式创建Mock:

const scrollIntoViewMock = jest.fn();
Element.prototype.scrollIntoView = scrollIntoViewMock();

单元测试可正常通过,但即使移除点击事件,测试依然通过,请问这是为什么?

组件代码

"use client";

import Image from "next/image";

import { CustomButton } from "@/components";

const Hero: React.FC = () => {
  const handleScroll = () => {
    const nextSection = document.getElementById("discover");

    if (nextSection) {
      nextSection.scrollIntoView({ behavior: "smooth" });
    }
  };

  return (
    <div className="hero">
      <div className="flex-1 pt-36 padding-x">
        <h1 className="hero__title">
          Find, book, rent a car—quick and super easy!
        </h1>

        <p className="hero__subtitle">
          Streamline your car rental experience with our effortless booking
          process.
        </p>

        <CustomButton
          title="Explore Cars"
          containerStyles="bg-primary-blue text-white rounded-full mt-10"
          handleClick={handleScroll}
        />
      </div>
      <div className="hero__image-container">
        <div className="hero__image">
          <Image src="/hero.png" alt="hero" fill className="object-contain" />
        </div>

        <div className="hero__image-overlay" />
      </div>
    </div>
  );
};

export default Hero;

通过的单元测试(含点击)

it("scrolls to the next section when Explore Cars button is clicked", () => {
  render(<Hero />);

  const scrollIntoViewMock = jest.fn();
  Element.prototype.scrollIntoView = scrollIntoViewMock();

  const buttonElement = screen.getByText("Explore Cars");
  userEvent.click(buttonElement);

  expect(scrollIntoViewMock).toHaveBeenCalled();
});

通过的单元测试(无点击)

it("scrolls to the next section when Explore Cars button is clicked", () => {
  render(<Hero />);

  const scrollIntoViewMock = jest.fn();
  Element.prototype.scrollIntoView = scrollIntoViewMock();

  expect(scrollIntoViewMock).toHaveBeenCalled();
});

原因

你的Mock写法犯了核心错误:scrollIntoViewMock()是直接调用了jest创建的mock函数,把函数执行的返回值(默认是undefined)赋值给了Element.prototype.scrollIntoView,而不是把mock函数本身绑定上去。

更关键的是,赋值时的scrollIntoViewMock()已经让这个mock函数被调用了一次,所以不管有没有点击按钮,断言expect(scrollIntoViewMock).toHaveBeenCalled()都会判定为通过——因为调用次数已经是1次了。


修复方案

  1. 修正Mock赋值方式:去掉mock函数后的括号,直接把函数本身赋值给scrollIntoView:
const scrollIntoViewMock = jest.fn();
Element.prototype.scrollIntoView = scrollIntoViewMock; // 移除后面的()
  1. 避免测试用例污染:建议在每个测试结束后恢复原生的scrollIntoView实现,防止影响其他测试:
afterEach(() => {
  jest.restoreAllMocks();
  // 或者手动恢复:delete Element.prototype.scrollIntoView;
});
  1. 优化断言严谨性:可以额外断言调用参数,确保逻辑完全符合预期:
it("scrolls to the next section when Explore Cars button is clicked", () => {
  render(<Hero />);

  const scrollIntoViewMock = jest.fn();
  Element.prototype.scrollIntoView = scrollIntoViewMock;

  const buttonElement = screen.getByText("Explore Cars");
  userEvent.click(buttonElement);

  expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: "smooth" });
});

修改后,只有点击按钮触发handleScroll时,mock函数才会被调用,移除点击事件的测试用例会正常失败,符合测试预期。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 23:15:36