Angular单元测试遇NotAllowedError:如何正确测试剪贴板复制函数?
解决剪贴板复制函数单元测试的权限拒绝问题
我有一个包含定时器与剪贴板文本复制功能的函数:
copyLink(linkAddress: string, index: number) { this.showTickForCopiedLink[index] = true; setTimeout(() => { this.showTickForCopiedLink[index] = false; }, 800); return navigator.clipboard.writeText(linkAddress); }
我为该函数编写了如下单元测试用例:
beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [SideBarNoteComponent], providers: [Clipboard], }).compileComponents(); })); beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(SideBarNoteComponent); component = fixture.componentInstance; fixture.detectChanges(); })); it('show tick icon for copied link for 800ms and then hide it', fakeAsync(() => { component.showTickForCopiedLink = [ false, false, false, ]; component.copyLink('link1', 1); expect(component.showTickForCopiedLink[1]).toBeTrue(); tick(799); expect(component.showTickForCopiedLink[1]).toBeTrue(); tick(2); expect(component.showTickForCopiedLink[1]).toBeFalse(); }));
该测试用例单独运行时可通过,但运行项目全量测试时,出现错误:
An error was thrown in afterAll Unhandled promise rejection: NotAllowedError: Write permission denied.
问题根源
全量测试时,navigator.clipboard.writeText会触发真实的剪贴板操作,而测试环境通常没有剪贴板操作权限,导致抛出NotAllowedError;同时这个被拒绝的Promise未被处理,引发了afterAll阶段的错误。
正确的测试方案
我们需要Mock掉剪贴板API,避免触发真实的权限校验,同时保证测试逻辑的完整性:
1. 针对Angular Clipboard服务的Mock方案
如果你的代码依赖Angular的Clipboard服务,修改测试模块配置,用Mock对象替代真实服务:
beforeEach(waitForAsync(() => { // 创建剪贴板服务的Mock,让writeText返回一个已resolve的Promise const mockClipboard = jasmine.createSpyObj('Clipboard', ['writeText']); mockClipboard.writeText.and.returnValue(Promise.resolve()); TestBed.configureTestingModule({ declarations: [SideBarNoteComponent], providers: [ { provide: Clipboard, useValue: mockClipboard } ], }).compileComponents(); }));
然后调整测试用例,同时验证剪贴板调用和定时器逻辑:
it('show tick icon for copied link for 800ms and then hide it, and calls clipboard write', fakeAsync(() => { component.showTickForCopiedLink = [false, false, false]; const clipboard = TestBed.inject(Clipboard) as jasmine.SpyObj<Clipboard>; component.copyLink('link1', 1); // 验证剪贴板方法被正确调用 expect(clipboard.writeText).toHaveBeenCalledWith('link1'); // 验证tick立即显示 expect(component.showTickForCopiedLink[1]).toBeTrue(); // 799ms后仍处于显示状态 tick(799); expect(component.showTickForCopiedLink[1]).toBeTrue(); // 累计801ms后隐藏 tick(2); expect(component.showTickForCopiedLink[1]).toBeFalse(); }) );
2. 针对原生navigator.clipboard的Mock方案
如果你的代码直接调用navigator.clipboard而非Angular服务,需要Mock全局的剪贴板对象:
beforeEach(() => { const mockClipboard = { writeText: jasmine.createSpy('writeText').and.returnValue(Promise.resolve()) }; // 覆盖全局navigator.clipboard Object.defineProperty(window.navigator, 'clipboard', { value: mockClipboard, writable: true }); });
关键说明
- Mock剪贴板操作后,不会触发真实的权限校验,彻底避免全量测试时的Promise拒绝错误
- 确保Mock的
writeText返回已resolve的Promise,防止出现未处理的Promise拒绝 - 测试逻辑同时覆盖了定时器的显示隐藏逻辑和剪贴板调用的正确性
内容的提问来源于stack exchange,提问作者Qiimiia
相关产品推荐
相关产品推荐

