如何在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次了。
修复方案
- 修正Mock赋值方式:去掉mock函数后的括号,直接把函数本身赋值给
scrollIntoView:
const scrollIntoViewMock = jest.fn(); Element.prototype.scrollIntoView = scrollIntoViewMock; // 移除后面的()
- 避免测试用例污染:建议在每个测试结束后恢复原生的
scrollIntoView实现,防止影响其他测试:
afterEach(() => { jest.restoreAllMocks(); // 或者手动恢复:delete Element.prototype.scrollIntoView; });
- 优化断言严谨性:可以额外断言调用参数,确保逻辑完全符合预期:
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
相关产品推荐
相关产品推荐

