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."
问题根源
- 逻辑顺序完全错误:测试中先
await getVideoMetadata,但该函数返回的Promise要等seeked事件触发才会resolve,而代码在await之后才触发事件,导致Promise永远处于pending状态,直接超时。 - 事件监听方式不匹配:原函数是直接给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 });
关键修复点说明
- 调整执行顺序:先调用函数拿到Promise对象,再触发事件让Promise resolve,最后await获取结果——彻底避免await挂起等待超时的问题。
- 匹配事件处理方式:原函数用的是
onXXX属性赋值,所以直接访问mock对象的onloadedmetadata和onseeked属性并调用即可触发逻辑。 - 补充中间逻辑验证:增加
currentTime的断言,确保函数的完整逻辑都被覆盖测试。
内容的提问来源于stack exchange,提问作者Dipesh Gupta
相关产品推荐
相关产品推荐

