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


87

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


您能提供一个显示您尝试过的功能的插件吗,然后我可以看看是否添加了缺失的片段。
君特Zöchbauer

Answers:


204

您的测试可能是:

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');
  }
}

1
如果它是我单击的锚而不是按钮,那么查询选择器是否只是按钮而不是按钮?我正在使用与该组件完全一样的东西,但使用的是'expect(value).toBe('hello');' 永远不会逃跑。我想知道是否是因为它是锚。
tallkid24 '02

我用间谍代替了真正的发射器,以一种更干净的测试方式更新了答案,并且我认为它应该可以工作(这是我对电子书中的样本所做的实际工作)。
cexbrayat

非常感谢!我是前端开发的新手,尤其是对其进行单元测试。这很有帮助。我什至不知道spyOn函数存在。
tallkid24 '02

如果使用TestComponent包装MyComponent,该如何测试?例如html = <my-component (myEventEmitter)="function($event)"></my-component>,在测试中,我这样做:tcb.overrideTemplate(TestComponent,html).createAsync(TestComponent)
bekos

1
精湛的答案-非常简洁,重点突出-一个非常有用的一般模式
danday74

48

您可以使用间谍,取决于您的风格。这是您轻松使用间谍来查看是否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,正如前面的评论中指出的那样,这可能会出现问题。从Angular 9.1.7开始,此答案仍然是一个很好的解决方案。如果有任何变化,请发表评论,我将更新此答案。感谢所有评论/主持的人。
约书亚(Joshua Michael Wagoner)

您不应该expect是实际的间谍(spyOn()通话结果)吗?
尤里

我错过了Spyon之后的“ component.buttonClick()”。此解决方案解决了我的问题。非常感谢!
珍珠

2

您可以@Output()在父模板中订阅或绑定发射器(如果它是),并在父组件中检查绑定是否已更新。您还可以调度click事件,然后应触发订阅。


因此,如果我确实喜欢Emitter.subscribe(data => {}); 我将如何获取next()输出?
tallkid24 '02

究竟。或TestComponenthas中的模板<my-component (someEmitter)="value=$event">(其中someEmitter@Output()valueTextComponent则应使用sendd事件来更新的属性。
君特Zöchbauer

0

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

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

-2

尽管票数最高的答案行之有效,但它们并没有表现出良好的测试实践,因此我认为我将通过一些实际示例来扩展Günter的答案

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

@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!');
  }
}

该组件是被测系统,监视它的一部分会破坏封装。角组件测试应该只了解三件事:

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

涉及直接在实例上调用方法或监视组件的某些部分的任何事情都与实现紧密联系在一起,并且会增加重构的难度-测试双打仅应用于协作者。在这种情况下,因为我们没有合作者,所以我们不需要任何模拟,间谍或其他测试双打。


一种测试方法是直接订阅发射器,然后调用click操作(请参阅具有输入和输出的组件):

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)并实际安装您的组件:

@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测试主机,因此我们可以确信我们不会过度耦合到实际测试的组件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.