有啥方法可以在 Angular2 中测试 EventEmitter?

Posted

技术标签:

【中文标题】有啥方法可以在 Angular2 中测试 EventEmitter?【英文标题】:Any way to test EventEmitter in Angular2?有什么方法可以在 Angular2 中测试 EventEmitter? 【发布时间】:2016-05-21 01:22:15 【问题描述】:

我有一个使用 EventEmitter 的组件,当点击页面上的某人时使用 EventEmitter。有什么方法可以在单元测试期间观察 EventEmitter,并使用 TestComponentBuilder 单击触发 EventEmitter.next() 方法的元素并查看发送了什么?

【问题讨论】:

您能否提供一个显示您尝试过的内容的插件,然后我可以看看添加缺少的部分。 【参考方案1】:

您的测试可能是:

it('should emit on click', () => 
   const fixture = TestBed.createComponent(MyComponent);
   // spy on event emitter
   const component = fixture.componentInstance; 
   spyOn(component.myEventEmitter, 'emit');

   // trigger the click
   const nativeElement = fixture.nativeElement;
   const button = nativeElement.querySelector('button');
   button.dispatchEvent(new Event('click'));

   fixture.detectChanges();

   expect(component.myEventEmitter.emit).toHaveBeenCalledWith('hello');
);

当你的组件是:

@Component( ... )
class MyComponent 
  @Output myEventEmitter = new EventEmitter<string>();

  buttonClick() 
    this.myEventEmitter.emit('hello');
  

【讨论】:

如果我单击的是锚点而不是按钮,那么查询选择器是否只是按钮而不是按钮?我正在使用与该组件完全相同的东西,但是 'expect(value).toBe('hello');'永远不会运行。我想知道是不是因为它是一个锚。 我用一种更简洁的测试方式更新了我的答案,使用间谍而不是真正的发射器,我认为它应该可以工作(这就是我对电子书中的示例所做的实际操作)。跨度> 这很好用,谢谢!我是前端开发的新手,尤其是单元测试。这很有帮助。我什至不知道存在 spyOn 函数。 如果使用 TestComponent 来包装 MyComponent,我该如何测试?例如 html = &lt;my-component (myEventEmitter)="function($event)"&gt;&lt;/my-component&gt; 并且在我做的测试中: tcb.overrideTemplate(TestComponent, html).createAsync(TestComponent) 极好的答案 - 非常简洁明了 - 一个非常有用的通用模式【参考方案2】:

你可以使用间谍,这取决于你的风格。以下是您如何轻松使用间谍来查看 emit 是否被解雇...

it('should emit on click', () => 
    spyOn(component.eventEmitter, 'emit');
    component.buttonClick();
    expect(component.eventEmitter.emit).toHaveBeenCalled();
    expect(component.eventEmitter.emit).toHaveBeenCalledWith('bar');
);

【讨论】:

我已经更新了不要不必要地使用 async 或 fakeAsync 的答案,正如之前的 cmets 所指出的那样,这可能是有问题的。从 Angular 9.1.7 开始,这个答案仍然是一个很好的解决方案。如果有任何变化,请发表评论,我会更新这个答案。感谢所有评论/主持的人。 你不应该 expect 真正的间谍(spyOn() 调用的结果)吗? 我错过了 Spyon 之后的“component.buttonClick()”。这个解决方案解决了我的问题。非常感谢!【参考方案3】:

尽管投票率最高的答案有效,但它们并没有展示出良好的测试实践,所以我想我会在 Günter's answer 上扩展一些实际示例。

假设我们有以下简单的组件:

@Component(
  selector: 'my-demo',
  template: `
    <button (click)="buttonClicked()">Click Me!</button>
  `
)
export class DemoComponent 
  @Output() clicked = new EventEmitter<string>();

  constructor()  

  buttonClicked(): void 
    this.clicked.emit('clicked!');
  

组件是被测系统,监视它的某些部分会破坏封装。 Angular 组件测试应该只知道三件事:

DOM(通过例如fixture.nativeElement.querySelector 访问); @Inputs 和 @Outputs 的名称;和 协作服务(通过 DI 系统注入)。

任何涉及在实例上直接调用方法或监视组件的某些部分的事情都与实现的耦合过于紧密,并且会增加重构的摩擦 - 测试替身应该只用于协作者。在这种情况下,由于我们没有合作者,我们不应该需要任何模拟、间谍或其他测试替身。


一种测试方法是直接订阅发射器,然后调用点击动作(参见Component with inputs and outputs):

describe('DemoComponent', () => 
  let component: DemoComponent;
  let fixture: ComponentFixture<DemoComponent>;

  beforeEach(async(() => 
    TestBed.configureTestingModule(
      declarations: [ DemoComponent ]
    )
    .compileComponents();
  ));

  beforeEach(() => 
    fixture = TestBed.createComponent(DemoComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  );

  it('should emit when clicked', () => 
    let emitted: string;
    component.clicked.subscribe((event: string) => 
      emitted = event;
    );

    fixture.nativeElement.querySelector('button').click();

    expect(emitted).toBe('clicked!');
  );
);

虽然它直接与组件实例交互,但 @Output 的名称是公共 API 的一部分,所以它并没有太紧耦合。


或者,您可以创建一个简单的测试主机(请参阅Component inside a test host)并实际安装您的组件:

@Component(
  selector: 'test-host',
  template: `
    <my-demo (clicked)="onClicked($event)"></my-demo>
  `
)
class TestHostComponent 
  lastClick = '';

  onClicked(value: string): void 
    this.lastClick = value;
  

然后在上下文中测试组件:

describe('DemoComponent', () => 
  let component: TestHostComponent;
  let fixture: ComponentFixture<TestHostComponent>;

  beforeEach(async(() => 
    TestBed.configureTestingModule(
      declarations: [ TestHostComponent, DemoComponent ]
    )
    .compileComponents();
  ));

  beforeEach(() => 
    fixture = TestBed.createComponent(TestHostComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  );

  it('should emit when clicked', () => 
    fixture.nativeElement.querySelector('button').click();

    expect(component.lastClick).toBe('clicked!');
  );
);

这里的componentInstance测试主机,因此我们可以确信我们没有过度耦合到我们实际测试的组件。

【讨论】:

这真的是最好的答案 优秀的答案!【参考方案4】:

您可以在父模板中订阅发射器或绑定到它,如果它是@Output(),并检查父组件是否更新了绑定。 你也可以发送一个点击事件,然后订阅就会触发。

【讨论】:

所以如果我喜欢emitter.subscribe (data => );我将如何获得 next() 输出? 没错。或者TestComponent 中的模板有&lt;my-component (someEmitter)="value=$event"&gt;(其中someEmitter@Output()),那么TextComponentvalue 属性应该使用发送的事件进行更新。【参考方案5】:

我需要测试发出数组的长度。所以这就是我在其他答案之上的做法。

expect(component.myEmitter.emit).toHaveBeenCalledWith([anything(), anything()]);

【讨论】:

以上是关于有啥方法可以在 Angular2 中测试 EventEmitter?的主要内容,如果未能解决你的问题,请参考以下文章

Angular2 测试:ComponentFixture 中的 DebugElement 和 NativeElement 对象有啥区别?

Angular 2+,有啥方法可以声明一个模块中的所有组件,然后在 app.module.ts 中导入?

Angular2 相比 Vue 有啥优势

html5中使用angular2的#attribute有啥用

使用 RxJava、RxKotlin 或 Even Kotlin 协程代替 On Click 监听器的接口有啥好处?

Angular2中的生产模式和开发模式有啥区别?