JavaScript单元测试中Mock Notification API的核心是替换全局Notification构造函数,需模拟其构造逻辑、requestPermission静态方法及permission属性,并支持断言调用参数与权限状态。

在 JavaScript 单元测试中 Mock Notification API,核心是**替换全局的 Notification 构造函数**,并控制其行为(如是否触发权限请求、是否显示通知),同时可断言调用参数。以下是几种常用且实用的方式:
1. 直接覆盖全局 Notification 构造函数
这是最直接、兼容性最好的方式,适合 Jest、Vitest 等主流测试框架:
- 在测试前用
jest.mock(Jest)或vi.mock(Vitest)模拟整个Notification类,或直接赋值给global.Notification - 需手动实现
requestPermission静态方法和实例的close方法(如果代码中用到) - 建议保存原始构造函数,测试后恢复,避免污染其他测试
示例(Jest):
beforeEach(() => {
// 保存原始 Notification
const originalNotification = global.Notification;
// Mock 构造函数
global.Notification = jest.fn().mockImplementation(function(title, options) {
this.title = title;
this.options = options;
this.close = jest.fn();
});
// Mock 静态方法
global.Notification.requestPermission = jest.fn().mockResolvedValue('granted');
global.Notification.permission = 'granted';
// 清理:确保每个测试独立
return () => {
global.Notification = originalNotification;
};
});
test('should create notification with correct title and icon', () => {
new Notification('Hello', { icon: '/icon.png' });
expect(global.Notification).toHaveBeenCalledWith('Hello', { icon: '/icon.png' });
expect(global.Notification).toHaveBeenCalledTimes(1);
});
2. 使用 jest.spyOn 模拟静态方法 + 构造函数
更精细地控制行为,比如模拟不同权限状态('denied'、'default'):
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 用
jest.spyOn(Notification, 'requestPermission')控制权限请求返回值 - 用
jest.fn().mockImplementation(...)替换构造函数逻辑 - 便于测试「用户拒绝权限时是否跳过发送」等分支逻辑
示例(测试拒绝权限场景):
test('should not show notification when permission is denied', async () => {
Object.defineProperty(Notification, 'permission', { value: 'denied', writable: true });
// 或 mock requestPermission 返回 'denied'
Notification.requestPermission = jest.fn().mockResolvedValue('denied');
// 调用你的通知函数,例如 showNotification()
await showNotification('Test');
expect(Notification).not.toHaveBeenCalled();
});
3. 封装可复用的 Mock 工具函数(推荐用于多测试文件)
避免重复代码,提升可维护性:
- 写一个
mockNotification()工具函数,接受permission、shouldThrow等参数 - 返回清理函数,自动还原全局状态
- 在
setupFilesAfterEnv或beforeEach中统一调用
示例(轻量封装):
export function mockNotification({ permission = 'granted', throws = false } = {}) {
const original = global.Notification;
global.Notification = jest.fn().mockImplementation(function(title, options) {
if (throws) throw new Error('Failed to show notification');
this.title = title;
this.options = options;
this.close = jest.fn();
});
global.Notification.requestPermission = jest.fn().mockResolvedValue(permission);
Object.defineProperty(global.Notification, 'permission', {
value: permission,
writable: true,
});
return () => {
global.Notification = original;
};
}
// 在测试中:
test('handles notification error gracefully', () => {
const cleanup = mockNotification({ throws: true });
try {
new Notification('Oops');
} catch (e) {
expect(e.message).toBe('Failed to show notification');
} finally {
cleanup();
}
});
4. 注意事项与常见陷阱
- 不能只 mock
requestPermission:构造函数本身也必须可 new,否则new Notification(...)会报错 -
Notification.permission是只读属性?实际在测试中可通过Object.defineProperty强制改写(注意设writable: true) - Vitest 用户注意:
vi.mock('node:process')等写法不适用于全局变量,应直接赋值globalThis.Notification - 若使用 TypeScript,需在类型声明中扩展
Window接口,或临时用@ts-ignore(仅测试文件)

















