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

如何正确编写Angular复制剪贴板指令的Jasmine测试用例?

解决Angular CopyClipboard指令测试报错问题

你实现的Angular CopyClipboard指令逻辑没问题,但测试代码存在mock方式错误,导致出现Expected a spy, but got Object({ writeText: <getter> })的报错。以下是错误原因分析和修正后的测试代码:

错误原因

navigator.clipboard的writeText是getter属性,用{ ...global.navigator.clipboard }复制对象时,无法正确复制getter,后续替换成spy的操作不会生效,测试时调用的还是原生方法,Jasmine自然识别不出这是一个spy。另外测试中误用了KeyboardEvent模拟点击事件,应该用MouseEvent。

修正后的完整测试代码

import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { configureTestSuite } from 'ng-bullet';

import { CopyClipboardDirective } from './copy-to-clipboard.directive';

describe('Copy to Clipboard directive', () => {
    let component: Dummy;
    let fixture: ComponentFixture<Dummy>;
    let directiveEl: any;
    let element: any;
    let writeSpy: jasmine.Spy;
    // 保存原生的clipboard对象,用于测试后恢复
    const originalClipboard = global.navigator.clipboard;

    configureTestSuite(() => {
        TestBed.configureTestingModule({
            declarations: [Dummy, CopyClipboardDirective],
        });
    });

    beforeEach(() => {
        // 1. 创建writeText的spy,并mock全局navigator.clipboard
        writeSpy = jasmine.createSpy('writeText');
        Object.defineProperty(global.navigator, 'clipboard', {
            value: { writeText: writeSpy },
            writable: true
        });

        fixture = TestBed.createComponent(Dummy);
        component = fixture.componentInstance;
        directiveEl = fixture.debugElement.query(By.directive(CopyClipboardDirective));
        element = fixture.debugElement.query(By.css('div'));
        fixture.detectChanges();
    });

    // 测试结束后恢复原生clipboard,避免影响其他测试
    afterEach(() => {
        Object.defineProperty(global.navigator, 'clipboard', {
            value: originalClipboard,
            writable: true
        });
    });

    it('should create an instance', () => {
        expect(directiveEl).toBeTruthy();
    });

    it('default event should be prevented', () => {
        // 用MouseEvent模拟点击事件,而非KeyboardEvent
        const event = new MouseEvent('click', { cancelable: true });

        element.nativeElement.dispatchEvent(event);
        expect(event.defaultPrevented).toBeTruthy();
    });

    it('copies the right text', () => {
        const event = new MouseEvent('click', { cancelable: true });

        directiveEl.nativeElement.dispatchEvent(event);
     
        expect(writeSpy).toHaveBeenCalledWith('copy text');
    });

    it('does not copy when payload is empty', () => {
        const event = new MouseEvent('click', { cancelable: true });
        // 先清空payload,再触发点击
        directiveEl.componentInstance.payload = '';
        element.nativeElement.dispatchEvent(event);
        
        expect(writeSpy).not.toHaveBeenCalled();
    });
});

@Component({
    template: `<div copyClipboard="copy text"></div>`
})
class Dummy {}

关键修改点

  • 正确mock全局clipboard:通过Object.defineProperty直接替换global.navigator.clipboard,确保指令调用的是spy方法
  • 测试前后环境恢复:在afterEach中恢复原生clipboard,避免污染其他测试用例
  • 修正事件类型:点击事件使用MouseEvent,符合真实场景
  • 修复空payload测试逻辑:先将payload设为空,再触发点击,验证不执行复制操作

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 11:52:31