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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 15:15:04