web-dev-qa-db-ja.com

Angularのユニットテストクリックイベント

Angular 2アプリに単体テストを追加しようとしています。私のコンポーネントの1つに、(click)ハンドラーを持つボタンがあります。ユーザーがボタンをクリックすると、.tsクラスファイルで定義されている関数が呼び出されます。その関数は、ボタンが押されたことを示すメッセージをconsole.logウィンドウに出力します。私の現在のテストコードは、console.logメッセージの印刷をテストします。

describe('Component: ComponentToBeTested', () => {
    var component: ComponentToBeTested;

    beforeEach(() => {
        component = new ComponentToBeTested();
        spyOn(console, 'log');
    });

    it('should call onEditButtonClick() and print console.log', () => {
        component.onEditButtonClick();
        expect(console.log).toHaveBeenCalledWith('Edit button has been clicked!);
    });
});

ただし、これはコントローラークラスのみをテストし、HTMLはテストしません。 onEditButtonClickが呼び出されたときにロギングが発生することをテストするだけではありません。また、ユーザーがコンポーネントのHTMLファイルで定義されている編集ボタンをクリックしたときにonEditButtonClickが呼び出されることをテストします。どうやってやるの?

45
Aiguo

私の目的は、ユーザーが編集ボタンをクリックしたときに 'onEditButtonClick'が呼び出されるかどうかを確認することであり、印刷されるconsole.logだけを確認することではありません。

最初にAngular TestBedを使用してテストをセットアップする必要があります。この方法で、実際にボタンをつかんでクリックできます。あなたがすることは、ちょうどテスト環境のために、あなたが@NgModuleをするように、モジュールを設定することです

import { TestBed, async, ComponentFixture } from '@angular/core/testing';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ ],
      declarations: [ TestComponent ],
      providers: [  ]
    }).compileComponents().then(() => {
      fixture = TestBed.createComponent(TestComponent);
      component = fixture.componentInstance;
    });
  }));
});

次に、onEditButtonClickメソッドをスパイし、ボタンをクリックして、メソッドが呼び出されたことを確認する必要があります

it('should', async(() => {
  spyOn(component, 'onEditButtonClick');

  let button = fixture.debugElement.nativeElement.querySelector('button');
  button.click();

  fixture.whenStable().then(() => {
    expect(component.onEditButtonClick).toHaveBeenCalled();
  });
}));

ここでは、ボタンクリックに非同期イベント処理が含まれているため、asyncテストを実行する必要があり、fixture.whenStable()を呼び出してイベントの処理を待機する必要があります。

関連項目:

70
Paul Samsotha

ブラウザ内のイベントは非同期であり、イベントループ/キューにプッシュされるため、'@angular/core/testing'が提供するasync/fakeAsync関数を使用してイベントをテストできます。

以下は、fakeAsyncを使用してクリックイベントをテストする非常に基本的な例です。

fakeAsync関数は、特別なfakeAsyncテストゾーンでテスト本体を実行することにより、線形コーディングスタイルを有効にします。

ここでは、クリックイベントによって呼び出されるメソッドをテストしています。

it('should', fakeAsync( () => {
    fixture.detectChanges();
    spyOn(componentInstance, 'method name'); //method attached to the click.
    let btn = fixture.debugElement.query(By.css('button'));
    btn.triggerEventHandler('click', null);
    tick(); // simulates the passage of time until all pending asynchronous activities finish
    fixture.detectChanges();
    expect(componentInstance.methodName).toHaveBeenCalled();
}));

以下は Angular docs が言うべきことです:

非同期に対するfakeAsyncの主な利点は、テストが同期しているように見えることです。目に見える制御の流れを妨げるthen(...)はありません。約束を返すfixture.whenStableはなくなり、tick()に置き換えられました

そこにare制限があります。たとえば、fakeAsync内からXHR呼び出しを行うことはできません。

33
Mav55

Angular 6を使用しています。私はMav55の答えに従いましたが、うまくいきました。ただし、fixture.detectChanges();が本当に必要かどうかを確認したかったので、それを削除しても機能しました。次に、tick();を削除して、機能するかどうかを確認しました。最後にfakeAsync()ラップからテストを削除しましたが、驚いたことに動作しました。

だから私はこれで終わった:

it('should call onClick method', () => {
  const onClickMock = spyOn(component, 'onClick');
  fixture.debugElement.query(By.css('button')).triggerEventHandler('click', null);
  expect(onClickMock).toHaveBeenCalled();
});

そして、それはうまくいきました。

2
Parziphal

同様の問題(詳細な説明)があり、元のtickと同じ(またはより大きい)ミリ秒でsetTimeout関数を使用して(jasmine-core: 2.52で)解決しましたコール。

たとえば、setTimeout(() => {...}, 2500);がある場合(2500ミリ秒後にトリガーされる)、tick(2500)を呼び出し、で問題を解決します。

Deleteボタンのクリックに対する反応として、コンポーネントにあったもの:

delete() {
    this.myService.delete(this.id)
      .subscribe(
        response => {
          this.message = 'Successfully deleted! Redirecting...';
          setTimeout(() => {
            this.router.navigate(['/home']);
          }, 2500); // I wait for 2.5 seconds before redirect
        });
  }

彼女は私の working テストです:

it('should delete the entity', fakeAsync(() => {
    component.id = 1; // preparations..
    component.getEntity(); // this one loads up the entity to my component
    tick(); // make sure that everything that is async is resolved/completed
    expect(myService.getMyThing).toHaveBeenCalledWith(1);
    // more expects here..
    fixture.detectChanges();
    tick();
    fixture.detectChanges();
    const deleteButton = fixture.debugElement.query(By.css('.btn-danger')).nativeElement;
    deleteButton.click(); // I've clicked the button, and now the delete function is called...

    tick(2501); // timeout for redirect is 2500 ms :)  <-- solution

    expect(myService.delete).toHaveBeenCalledWith(1);
    // more expects here..
  }));

追伸fakeAsyncおよびテストの一般的な非同期に関する優れた説明は、ここにあります: Angular 2-Julie Ralphによる8からのテスト戦略に関するビデオ:10、持続時間4分:)

1
Casper