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

如何用Jest模拟HTML Audio元素的onended等事件?

模拟HTML Audio元素的onended/onerror等事件触发(TypeScript + Jest)

我正在测试基于HTML audio元素的自定义音频播放器,但没法模拟onended、onerror这类事件的触发。已经完成了常规方法(比如pause/play)的模拟和事件处理,但事件始终没被触发。有人实现过类似需求吗?

附现有代码片段:

export const mocks = {
  Audio: {
    pause: jest.fn(),
    play: jest.fn()
    // onended: --> 这是我想知道如何调用/模拟的事件
  }
}

解决方案

方法1:完善Audio Mock,兼容两种事件绑定方式

原生HTMLMediaElement的事件支持两种绑定逻辑:直接赋值onended这类回调属性,或者用addEventListener绑定。你的Mock需要同时覆盖这两种场景,还要提供手动触发事件的能力:

export const mocks = {
  Audio: jest.fn().mockImplementation(() => {
    // 存储addEventListener绑定的回调
    const eventListeners: Record<string, Function[]> = {};

    return {
      pause: jest.fn(),
      play: jest.fn(),
      // 模拟addEventListener
      addEventListener: jest.fn((event: string, callback: Function) => {
        if (!eventListeners[event]) {
          eventListeners[event] = [];
        }
        eventListeners[event].push(callback);
      }),
      // 模拟直接赋值的事件回调属性
      onended: null as (() => void) | null,
      onerror: null as ((e: ErrorEvent) => void) | null,
      // 手动触发事件的方法
      triggerEvent: jest.fn((event: string, eventData?: any) => {
        // 先触发addEventListener绑定的回调
        if (eventListeners[event]) {
          eventListeners[event].forEach(callback => callback(eventData));
        }
        // 再触发直接赋值的onXXX回调
        if (event === 'ended' && (this as any).onended) {
          (this as any).onended();
        }
        if (event === 'error' && (this as any).onerror) {
          (this as any).onerror(eventData || new ErrorEvent('error'));
        }
      })
    };
  })
};

方法2:在测试中触发事件

实例化Mock的Audio对象后,直接调用triggerEvent就能模拟事件触发:

// 假设你的播放器里创建了Audio实例
const audioInstance = new mocks.Audio();

// 触发ended事件
audioInstance.triggerEvent('ended');

// 触发带错误信息的error事件
const errorEvent = new ErrorEvent('error', { error: new Error('音频加载失败') });
audioInstance.triggerEvent('error', errorEvent);

方法3:模拟原生dispatchEvent逻辑(更贴近真实场景)

如果你的播放器主要用addEventListener绑定事件,可以模拟原生的dispatchEvent方法,更符合真实DOM行为:

export const mocks = {
  Audio: jest.fn().mockImplementation(() => {
    const listeners: Record<string, EventListener[]> = {};

    return {
      pause: jest.fn(),
      play: jest.fn(),
      addEventListener: jest.fn((type: string, listener: EventListener) => {
        if (!listeners[type]) listeners[type] = [];
        listeners[type].push(listener);
      }),
      removeEventListener: jest.fn((type: string, listener: EventListener) => {
        if (listeners[type]) {
          listeners[type] = listeners[type].filter(l => l !== listener);
        }
      }),
      dispatchEvent: jest.fn((event: Event) => {
        const type = event.type;
        if (listeners[type]) {
          listeners[type].forEach(listener => listener(event));
          return true;
        }
        return false;
      })
    };
  })
};

测试时使用:

const audio = new mocks.Audio();
// 触发ended事件
audio.dispatchEvent(new Event('ended'));
// 触发error事件
audio.dispatchEvent(new ErrorEvent('error', { message: '播放出错' }));

注意事项

  • TypeScript环境下,若出现类型报错,可以用类型断言(as any)或者扩展HTMLAudioElement接口来补充Mock的属性/方法定义
  • 确保你的自定义播放器里的事件绑定逻辑,和Mock支持的方式一致(比如用了onended就别只MockaddEventListener)

内容的提问来源于stack exchange,提问作者Mauricio Ruiz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 07:05:29