在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
相关产品推荐
相关产品推荐

