在Jest/React单元测试中如何给自定义Hook传函数并实现100%覆盖率
前置依赖
我们使用React官方推荐的测试库完成测试,先安装必要依赖:
npm install @testing-library/react jest -D
测试覆盖目标
要达到100%覆盖率,需要覆盖所有逻辑分支:
- Hook挂载时成功注册
popstate事件监听 - 浏览器前进/后退触发
popstate事件时,传入的回调函数正常执行,参数正确透传 - 传入的回调函数更新时,Hook内部会同步使用最新的回调
- Hook卸载时成功移除
popstate事件监听 - 回调为空时的可选链逻辑不会抛出异常
完整测试代码
新建测试文件useBrowserFwdBackButton.test.ts,代码如下:
import { renderHook } from '@testing-library/react'; import useBrowserFwdBackButton from './useBrowserFwdBackButton'; // 提前mock window的事件监听方法 beforeAll(() => { jest.spyOn(window, 'addEventListener'); jest.spyOn(window, 'removeEventListener'); }); // 每次测试后重置mock调用记录 afterEach(() => { jest.clearAllMocks(); }); // 所有测试结束后恢复原始方法 afterAll(() => { jest.restoreAllMocks(); }); describe('useBrowserFwdBackButton', () => { it('挂载时注册popstate事件监听', () => { const mockFn = jest.fn(); renderHook(() => useBrowserFwdBackButton(mockFn)); // 验证addEventListener被调用,参数正确 expect(window.addEventListener).toHaveBeenCalledWith('popstate', expect.any(Function)); }); it('触发popstate事件时,传入的回调函数被调用且参数正确透传', () => { const mockFn = jest.fn(); renderHook(() => useBrowserFwdBackButton(mockFn)); // 模拟触发popstate事件,带自定义参数 const mockEvent = new PopStateEvent('popstate', { state: { test: '123' } }); window.dispatchEvent(mockEvent); // 验证回调被调用,参数正确 expect(mockFn).toHaveBeenCalledTimes(1); expect(mockFn).toHaveBeenCalledWith(mockEvent); }); it('传入的回调更新后,触发事件会调用最新的回调', () => { const oldMockFn = jest.fn(); const newMockFn = jest.fn(); // 这里用initialProps传递参数,方便后续更新参数 const { rerender } = renderHook((props) => useBrowserFwdBackButton(props.callback), { initialProps: { callback: oldMockFn } }); // 先触发一次事件,验证旧回调被调用 window.dispatchEvent(new PopStateEvent('popstate')); expect(oldMockFn).toHaveBeenCalledTimes(1); expect(newMockFn).not.toHaveBeenCalled(); // 更新传入的回调 rerender({ callback: newMockFn }); // 再触发一次事件 window.dispatchEvent(new PopStateEvent('popstate')); // 验证新回调被调用,旧回调不会再被调用 expect(oldMockFn).toHaveBeenCalledTimes(1); expect(newMockFn).toHaveBeenCalledTimes(1); }); it('卸载时移除popstate事件监听', () => { const mockFn = jest.fn(); const { unmount } = renderHook(() => useBrowserFwdBackButton(mockFn)); // 调用卸载方法 unmount(); // 验证removeEventListener被调用,参数和注册时一致 expect(window.removeEventListener).toHaveBeenCalledWith('popstate', expect.any(Function)); }); it('回调为空时触发事件不会抛出异常', () => { // 不传回调/传undefined,覆盖可选链逻辑 renderHook(() => useBrowserFwdBackButton(undefined)); expect(() => window.dispatchEvent(new PopStateEvent('popstate'))).not.toThrow(); }); });
常见问题说明
如何给Hook传递函数参数
测试自定义Hook时,有两种传参方式:
- 直接在
renderHook的回调函数里传入写死的参数,适合固定参数的测试场景:
renderHook(() => useBrowserFwdBackButton(yourMockFunction))
- 通过
initialProps动态传参,适合需要后续更新参数测试的场景,如上面测试回调更新的用例所示。
覆盖率验证
执行测试时添加覆盖率参数即可查看覆盖率:
npx jest useBrowserFwdBackButton.test.ts --coverage
上述用例执行后可达到100%的行、分支、函数覆盖率。
内容的提问来源于stack exchange,提问作者Muhammad Maaz Sheikh
相关产品推荐
相关产品推荐

