Angular2でEventEmitterをテストする方法はありますか?


88

EventEmitterを使用するコンポーネントがあり、ページ上の誰かがクリックされたときにEventEmitterが使用されます。単体テスト中にEventEmitterを監視し、TestComponentBuilderを使用してEventEmitter.next()メソッドをトリガーする要素をクリックし、何が送信されたかを確認する方法はありますか?


あなたが試したことを示すプランカーを提供できますか?それなら私は不足している部分を追加するために見ることができます。
ギュンターZöchbauer

回答:


212

あなたのテストは次のようになります:

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 2016

実際のエミッターの代わりにスパイを使用して、よりクリーンなテスト方法で回答を更新しました。これでうまくいくはずです(これが、私の電子書籍のサンプルに対して実際に行っていることです)。
cexbrayat 2016

これは大いに感謝します!私はフロントエンド開発、特にユニットテストに不慣れです。これは大いに役立ちます。spyOn関数が存在することすら知りませんでした。
tallkid24 2016

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を不必要に使用しないように回答を更新しました。この答えは、Angular9.1.7の時点でも優れた解決策です。何か変更がありましたら、コメントを残してください。この回答を更新します。コメント/モデレートしてくれたすべての人に感謝します。
Joshua MichaelWagoner20年

あなたexpectは実際のスパイ(spyOn()電話の結果)ではありませんか?
百合

Spyonの後に「component.buttonClick()」を見逃しました。この解決策は私の問題を解決しました。どうもありがとう!
パール

2

@Output()親テンプレートでエミッターをサブスクライブするか、エミッターにバインドすることができます。また、バインドが更新されているかどうかを親コンポーネントで確認できます。クリックイベントをディスパッチして、サブスクリプションを起動することもできます。


したがって、emitter.subscribe(data => {});が好きだった場合。next()出力を取得するにはどうすればよいですか?
tallkid24 2016

丁度。または、テンプレートをTestComponent持っている<my-component (someEmitter)="value=$event">(ここsomeEmitter@Output()当時)valueのプロパティTextComponentて送信イベントで更新する必要があります。
ギュンターZöchbauer

0

放出された配列の長さをテストする必要がありました。だから、これは私が他の回答の上にこれをした方法です。

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

0

最高得票数の回答は機能しますが、優れたテスト手法を示していないため、いくつかの実用的な例を使用して、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!');
  }
}

コンポーネントはテスト中のシステムであり、その一部をスパイするとカプセル化が破られます。Angularコンポーネントのテストでは、次の3つのことだけを知っておく必要があります。

  • DOM(eg経由でアクセスfixture.nativeElement.querySelector);
  • @Inputsと@Outputsの名前。そして
  • コラボレーションサービス(DIシステムを介して注入)。

インスタンスでメソッドを直接呼び出したり、コンポーネントの一部をスパイしたりすることは、実装と密接に関連しているため、リファクタリングに摩擦が加わります。テストダブルは共同作業者にのみ使用する必要があります。この場合、共同編集者がいないためモック、スパイ、その他のテストダブルは必要ありません。


これをテストする1つの方法は、エミッターに直接サブスクライブしてから、クリックアクションを呼び出すことです(入力と出力のあるコンポーネントを参照)。

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({
  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ここで、テストホスト我々は過度に私たちが実際にテストしているコンポーネントに結合されていない確信することができて、。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.