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

Jest测试视频元数据提取函数时触发超时错误

Jest测试视频元数据提取函数超时问题修复

我写了一个从HTMLVideoElement提取宽高元数据的async函数getVideoMetadata,生产环境运行正常,用来生成视频缩略图,但Jest测试时调用该函数直接触发超时(超过5000ms),延长超时也没用。

原函数代码

export async function getVideoMetadata(
  videoBlobUrl: string,
  videoElement: HTMLVideoElement,
): Promise<{ width: number; height: number }> {
  // Trigger video load
  return new Promise<{ width: number; height: number }>((resolve, reject) => {
    videoElement.onloadedmetadata = () => {
      videoElement.width = videoElement.videoWidth;
      videoElement.height = videoElement.videoHeight;
      videoElement.currentTime = videoElement.duration * 0.25;
    };

    videoElement.onseeked = () => {
      resolve({ width: videoElement.videoWidth, height: videoElement.videoHeight });
    };

    videoElement.onerror = () => {
      reject(`Error loading video`);
    };
    videoElement.src = videoBlobUrl;
  });
}

原错误测试代码

it('should return video metadata', async () => {
  // Mock the videoBlobUrl
  const videoBlobUrl = 'https://example.com/mock-video-url.mp4';

  // Mock the HTMLVideoElement
  const videoElementMock: HTMLVideoElement = {
    onloadedmetadata: null,
    onseeked: null,
    onerror: null,
    width: 0,
    height: 0,
    videoWidth: 640, // Mock video width
    videoHeight: 480, // Mock video height
    duration: 10, // Mock video duration
    currentTime: 0,
    src: '',
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
    play: jest.fn(),
    pause: jest.fn(),
    load: jest.fn(),
  };

  // Spy on addEventListener to capture the event handlers
  const addEventListenerSpy = jest.spyOn(videoElementMock, 'addEventListener');

  // Call the function with the mocked data
  const result = await getVideoMetadata(videoBlobUrl, videoElementMock);

  // Assertions
  expect(videoElementMock.src).toBe(videoBlobUrl);
  expect(addEventListenerSpy).toHaveBeenCalledWith('loadedmetadata', expect.any(Function));
  expect(addEventListenerSpy).toHaveBeenCalledWith('seeked', expect.any(Function));

  // Trigger the loadedmetadata event
  const loadedMetadataHandler = (addEventListenerSpy as jest.Mock).mock.calls.find(
    (call) => call[0] === 'loadedmetadata',
  )[1] as EventListener;
  loadedMetadataHandler(new Event('loadedmetadata'));

  // Trigger the seeked event
  const seekedHandler = (addEventListenerSpy as jest.Mock).mock.calls.find(
    (call) => call[0] === 'seeked',
  )[1] as EventListener;
  seekedHandler(new Event('seeked'));

  // Validate the result
  expect(result).toEqual({width:640, height:480});
});

错误信息

thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."

问题根源

  1. 逻辑顺序完全错误:测试中先await getVideoMetadata,但该函数返回的Promise要等seeked事件触发才会resolve,而代码在await之后才触发事件,导致Promise永远处于pending状态,直接超时。
  2. 事件监听方式不匹配:原函数是直接给videoElement的onloadedmetadata、onseeked属性赋值事件处理函数,而测试代码却去spyaddEventListener,根本捕获不到实际的处理逻辑,自然无法触发resolve。

修复后的测试代码

it('should return video metadata', async () => {
  const videoBlobUrl = 'https://example.com/mock-video-url.mp4';

  const videoElementMock: HTMLVideoElement = {
    onloadedmetadata: null,
    onseeked: null,
    onerror: null,
    width: 0,
    height: 0,
    videoWidth: 640,
    videoHeight: 480,
    duration: 10,
    currentTime: 0,
    src: '',
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
    play: jest.fn(),
    pause: jest.fn(),
    load: jest.fn(),
  };

  // 先调用函数,此时会给mock对象的事件属性赋值
  const promise = getVideoMetadata(videoBlobUrl, videoElementMock);

  // 触发loadedmetadata事件,模拟元数据加载完成
  videoElementMock.onloadedmetadata?.call(videoElementMock, new Event('loadedmetadata'));
  // 触发seeked事件,让Promise resolve
  videoElementMock.onseeked?.call(videoElementMock, new Event('seeked'));

  // 现在await可以正常拿到结果
  const result = await promise;

  // 验证核心逻辑
  expect(videoElementMock.src).toBe(videoBlobUrl);
  expect(result).toEqual({ width: 640, height: 480 });
  // 额外验证中间逻辑:currentTime是否被正确设置
  expect(videoElementMock.currentTime).toBe(2.5); // 10 * 0.25
});

关键修复点说明

  1. 调整执行顺序:先调用函数拿到Promise对象,再触发事件让Promise resolve,最后await获取结果——彻底避免await挂起等待超时的问题。
  2. 匹配事件处理方式:原函数用的是onXXX属性赋值,所以直接访问mock对象的onloadedmetadata和onseeked属性并调用即可触发逻辑。
  3. 补充中间逻辑验证:增加currentTime的断言,确保函数的完整逻辑都被覆盖测试。

内容的提问来源于stack exchange,提问作者Dipesh Gupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 12:22:11