如何使用Jest和Enzyme模拟返回滚动元素数据的自定义Hook
如何用Jest和Enzyme模拟自定义useOnScroll Hook?
针对你的场景,有三种常见测试方式,分别对应不同的测试需求:
一、测试组件时Mock Hook返回值
如果只需验证组件根据Hook返回的scrollTop渲染正确类名,无需测试Hook内部逻辑,可直接用Jest mock整个Hook:
import React from 'react'; import { shallow } from 'enzyme'; import MyComponent from './MyComponent'; import useOnScroll from '../utils/useOnScroll'; // Mock自定义Hook jest.mock('../utils/useOnScroll', () => jest.fn()); describe('MyComponent', () => { it('滚动容器scrollTop>0时,header添加scrolled类', () => { // 指定Hook返回的scrollTop为100 useOnScroll.mockReturnValue({ scrollTop: 100, direction: 'down' }); const wrapper = shallow(<MyComponent />); const header = wrapper.find('[data-test="btmSheet-header"]'); expect(header.hasClass('scrolled')).toBe(true); }); it('滚动容器scrollTop=0时,header不添加scrolled类', () => { useOnScroll.mockReturnValue({ scrollTop: 0, direction: 'up' }); const wrapper = shallow(<MyComponent />); const header = wrapper.find('[data-test="btmSheet-header"]'); expect(header.hasClass('scrolled')).toBe(false); }); });
二、模拟真实滚动行为测试组件与Hook的交互
如果需要验证真实滚动触发Hook逻辑后组件的变化,不mock Hook,直接模拟DOM滚动事件:
import React from 'react'; import { mount } from 'enzyme'; import MyComponent from './MyComponent'; describe('MyComponent滚动行为', () => { it('容器向下滚动时,header添加scrolled类', () => { const wrapper = mount(<MyComponent />); const scrollContainer = wrapper.find('[data-test="btmSheet-body"]').getDOMNode(); // 设置scrollTop并触发scroll事件 scrollContainer.scrollTop = 50; scrollContainer.dispatchEvent(new Event('scroll')); // 强制更新组件,确保Hook状态更新同步 wrapper.update(); const header = wrapper.find('[data-test="btmSheet-header"]'); expect(header.hasClass('scrolled')).toBe(true); }); it('容器scrollTop重置为0时,header移除scrolled类', () => { const wrapper = mount(<MyComponent />); const scrollContainer = wrapper.find('[data-test="btmSheet-body"]').getDOMNode(); // 先滚动到50 scrollContainer.scrollTop = 50; scrollContainer.dispatchEvent(new Event('scroll')); wrapper.update(); // 再重置scrollTop为0 scrollContainer.scrollTop = 0; scrollContainer.dispatchEvent(new Event('scroll')); wrapper.update(); const header = wrapper.find('[data-test="btmSheet-header"]'); expect(header.hasClass('scrolled')).toBe(false); }); });
三、单独测试useOnScroll Hook的逻辑
如果需要验证Hook自身的逻辑(比如滚动方向判断、事件监听管理),可以用@testing-library/react的renderHook工具:
import { renderHook, act } from '@testing-library/react'; import useOnScroll from './useOnScroll'; describe('useOnScroll Hook', () => { it('向下滚动时返回正确的scrollTop和direction', () => { // 创建模拟DOM元素 const mockEl = document.createElement('div'); const elRef = { current: mockEl }; const { result } = renderHook(() => useOnScroll(elRef)); // 模拟滚动事件 act(() => { mockEl.scrollTop = 100; mockEl.dispatchEvent(new Event('scroll')); }); expect(result.current.scrollTop).toBe(100); expect(result.current.direction).toBe('down'); }); it('向上滚动时返回direction为up', () => { const mockEl = document.createElement('div'); const elRef = { current: mockEl }; const { result } = renderHook(() => useOnScroll(elRef)); // 先向下滚动到100 act(() => { mockEl.scrollTop = 100; mockEl.dispatchEvent(new Event('scroll')); }); // 再向上滚动到50 act(() => { mockEl.scrollTop = 50; mockEl.dispatchEvent(new Event('scroll')); }); expect(result.current.scrollTop).toBe(50); expect(result.current.direction).toBe('up'); }); it('正确添加和移除scroll事件监听', () => { const mockEl = document.createElement('div'); const addSpy = jest.spyOn(mockEl, 'addEventListener'); const removeSpy = jest.spyOn(mockEl, 'removeEventListener'); const elRef = { current: mockEl }; const { unmount } = renderHook(() => useOnScroll(elRef)); // 验证添加了监听 expect(addSpy).toHaveBeenCalledWith('scroll', expect.any(Function)); // 卸载Hook,验证移除了监听 unmount(); expect(removeSpy).toHaveBeenCalledWith('scroll', expect.any(Function)); }); });
注意事项
- 测试真实滚动行为时,必须用
mount而非shallow,因为需要获取真实DOM元素触发事件。 - 触发滚动事件后,调用
wrapper.update()确保组件状态同步更新。 - 测试Hook时,用
act()包裹事件触发逻辑,确保React状态更新完成后再断言。
内容的提问来源于stack exchange,提问作者Sushmit Sagar
相关产品推荐
相关产品推荐

