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

在Jest/React单元测试中如何给自定义Hook传函数并实现100%覆盖率

自定义Hook useBrowserFwdBackButton 单元测试实现方案

前置依赖

我们使用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时,有两种传参方式:

  1. 直接在renderHook的回调函数里传入写死的参数,适合固定参数的测试场景:
renderHook(() => useBrowserFwdBackButton(yourMockFunction))
  1. 通过initialProps动态传参,适合需要后续更新参数测试的场景,如上面测试回调更新的用例所示。

覆盖率验证

执行测试时添加覆盖率参数即可查看覆盖率:

npx jest useBrowserFwdBackButton.test.ts --coverage

上述用例执行后可达到100%的行、分支、函数覆盖率。

内容的提问来源于stack exchange,提问作者Muhammad Maaz Sheikh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 19:15:05