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

在Vitest中监听useReportWebVitals遇问题,如何测试回调逻辑?

问题分析

  • vi.spyOn报错是因为next/web-vitals导出的useReportWebVitals是不可配置属性,无法被spyOn覆盖。
  • 改用vi.mock后未进入回调,是因为默认mock函数不会主动执行传入的回调,需手动实现触发逻辑。

解决方案

1. 正确模拟依赖模块

用vi.mock模拟next/web-vitals以捕获回调,同时模拟opentelemetry的核心方法用于断言:

// 测试文件顶部
import opentelemetry from '@opentelemetry/api';
import { useWebVitals } from '@/components/shared/hooks/useWebVitals';
import { ROUTES } from '@/lib/routes';
import { renderHook } from '@testing-library/react';
import * as webVitals from 'next/web-vitals';

// 捕获useReportWebVitals传入的回调
let capturedCallback: Parameters<typeof webVitals.useReportWebVitals>[0] | undefined;
vi.mock('next/web-vitals', () => ({
  useReportWebVitals: (callback: Parameters<typeof webVitals.useReportWebVitals>[0]) => {
    capturedCallback = callback;
  },
}));

// 模拟opentelemetry trace链路
const mockEndSpan = vi.fn();
const mockStartSpan = vi.fn().mockReturnValue({ end: mockEndSpan });
const mockGetTracer = vi.fn().mockReturnValue({ startSpan: mockStartSpan });
vi.spyOn(opentelemetry.trace, 'getTracer').mockImplementation(mockGetTracer);

2. 测试核心业务逻辑

手动触发捕获的回调,验证startTime计算、路由属性、阈值过滤等逻辑:

describe('useWebVitals', () => {
  beforeEach(() => {
    // 重置mock状态
    vi.clearAllMocks();
    capturedCallback = undefined;
  });

  it('计算正确的startTime并设置路由属性', () => {
    const testRoute = ROUTES.HOME;
    const testValue = 1200;
    const testMetric = 'LCP';
    
    // 渲染钩子并捕获回调
    renderHook(() => useWebVitals(testRoute));
    expect(capturedCallback).toBeDefined();
    
    // 固定系统时间,确保startTime计算可预测
    const mockCurrentTime = 1700000000000;
    vi.useFakeTimers();
    vi.setSystemTime(mockCurrentTime);
    
    // 触发回调
    capturedCallback!({ name: testMetric, value: testValue });
    
    // 验证startTime和路由参数
    const expectedStartTime = mockCurrentTime - Math.round(testValue);
    expect(mockStartSpan).toHaveBeenCalledWith(`metric-${testMetric}`, expect.objectContaining({
      startTime: expectedStartTime,
      attributes: { 'http.route': testRoute },
    }));
    
    // 验证opentelemetry调用链路
    expect(opentelemetry.trace.getTracer).toHaveBeenCalled();
    expect(mockEndSpan).toHaveBeenCalled();
    
    vi.useRealTimers();
  });

  it('忽略超过阈值的metric值', () => {
    renderHook(() => useWebVitals(ROUTES.HOME));
    
    // CLS阈值为0.35,传入超过阈值的值
    capturedCallback!({ name: 'CLS', value: 0.4 });
    
    // 验证未触发opentelemetry上报
    expect(mockStartSpan).not.toHaveBeenCalled();
  });

  it('路由为空时使用默认值', () => {
    renderHook(() => useWebVitals(''));
    
    capturedCallback!({ name: 'TTFB', value: 300 });
    
    expect(mockStartSpan).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
      attributes: { 'http.route': 'Home' },
    }));
  });
});

关键说明

  • 通过vi.mock模拟useReportWebVitals时主动保存回调,实现手动触发测试内部逻辑。
  • 用vi.useFakeTimers固定时间,避免时间波动导致startTime断言失败。
  • 模拟opentelemetry方法,精准验证调用参数和链路完整性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 02:52:13