開発者ドキュメント

AngularテストでwaitForAsyncとfakeAsyncを使用する方法

序章

Angular 2+は、非同期コードをテストするためのasyncおよびfakeAsyncユーティリティを提供します。 これにより、Angularユニットと統合テストの記述がはるかに簡単になります。

この記事では、サンプルテストとともにwaitForAsyncfakeAsyncを紹介します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

このチュートリアルは、ノードv16.4.0、npm v7.19.0、および@angular/corev12.1.1で検証されました。

プロジェクトの設定

まず、@angular/cliを使用して新しいプロジェクトを作成します。

  1. ng new angular-async-fakeasync-example

次に、新しく作成されたプロジェクトディレクトリに移動します。

  1. cd angular-async-fakeasync-example

これにより、app.component.htmlapp.compontent.ts、およびapp.component.spec.tsファイルを含む新しいAngularプロジェクトが作成されます。

waitForAsyncを使用したテスト

waitForAsyncユーティリティは、Promiseをインターセプトする専用のテストゾーンでコードを実行するようにAngularに指示します。 compileComponentsを使用する場合の、Angularでのユニットテストの概要で非同期ユーティリティについて簡単に説明しました。

whenStableユーティリティを使用すると、すべての約束が解決されて期待を実行できるようになるまで待つことができます。

最初にapp.component.tsを開き、Promiseからresolvetitleを使用します。

src / app / app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title!: string;

  setTitle() {
    new Promise(resolve => {
      resolve('Async Title!');
    }).then((val: any) => {
      this.title = val;
    });
  }
}

次に、app.component.htmlを開き、h1およびbuttonと交換します。

src / app / app.component.html
<h1>
  {{ title }}
</h1>

<button (click)="setTitle()" class="set-title">
  Set Title
</button>

ボタンがクリックされると、titleプロパティがpromiseを使用して設定されます。

そして、waitForAsyncwhenStableを使用してこの機能をテストする方法は次のとおりです。

src / app / app.component.spec.ts
import { TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  });

  it('should display title', waitForAsync(() => {
    const fixture = TestBed.createComponent(AppComponent);

    fixture.debugElement
      .query(By.css('.set-title'))
      .triggerEventHandler('click', null);

    fixture.whenStable().then(() => {
      fixture.detectChanges();
      const value = fixture.debugElement
        .query(By.css('h1'))
        .nativeElement
        .innerText;
      expect(value).toEqual('Async Title!');
    });
  }));
});

注:実際のアプリでは、リクエストからバックエンドAPIへの応答などの便利なものを実際に待機するPromiseがあります。

この時点で、テストを実行できます。

  1. ng test

これにより、'should display title'テスト結果が正常に生成されます。

fakeAsyncを使用したテスト

asyncの問題は、テストで実際の待機を導入する必要があることです。これにより、テストが非常に遅くなる可能性があります。 fakeAsyncが助けになり、非同期コードを同期的にテストするのに役立ちます。

fakeAsyncを示すために、簡単な例から始めましょう。 コンポーネントテンプレートに次のような値をインクリメントするボタンがあるとします。

src / app / app.component.html
<h1>
  {{ incrementDecrement.value }}
</h1>

<button (click)="increment()" class="increment">
  Increment
</button>

次のようなコンポーネントクラスのincrementメソッドを呼び出します。

src / app / app.component.ts
import { Component } from '@angular/core';
import { IncrementDecrementService } from './increment-decrement.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(public incrementDecrement: IncrementDecrementService) { }

  increment() {
    this.incrementDecrement.increment();
  }
}

そして、このメソッド自体がincrementDecrementサービスのメソッドを呼び出します。

  1. ng generate service increment-decrement

これには、setTimeoutを使用して非同期化されたincrementメソッドがあります。

src / app / increment-decrement.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class IncrementDecrementService {
  value = 0;
  message!: string;

  increment() {
    setTimeout(() => {
      if (this.value < 15) {
        this.value += 1;
        this.message = '';
      } else {
        this.message = 'Maximum reached!';
      }
    }, 5000); // wait 5 seconds to increment the value
  }
}

明らかに、実際のアプリでは、この非同期性はさまざまな方法で導入できます。

次に、fakeAsynctickユーティリティを使用して統合テストを実行し、テンプレートで値が増分されることを確認します。

src / app / app.component.spec.ts
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ]
    }).compileComponents();
  });

  it('should increment in template after 5 seconds', fakeAsync(() => {
    const fixture = TestBed.createComponent(AppComponent);

    fixture.debugElement
      .query(By.css('button.increment'))
      .triggerEventHandler('click', null);

    tick(2000);

    fixture.detectChanges();
    const value1 = fixture.debugElement.query(By.css('h1')).nativeElement.innerText;
    expect(value1).toEqual('0'); // value should still be 0 after 2 seconds

    tick(3000);

    fixture.detectChanges();
    const value2 = fixture.debugElement.query(By.css('h1')).nativeElement.innerText;
    expect(value2).toEqual('1'); // 3 seconds later, our value should now be 1
  }));
});

tickユーティリティがfakeAsyncブロック内でどのように使用され、時間の経過をシミュレートしているかに注目してください。 tickに渡される引数は、通過するミリ秒数であり、これらはテスト内で累積されます。

ノート: Tick can also be used with no argument, in which case it waits until all the microtasks are done (when promises are resolved for example).

この時点で、テストを実行できます。

  1. ng test

これにより、'should increment in template after 5 seconds'テスト結果が正常に生成されます。

そのように通過時間を指定することは、すぐに面倒になり、どのくらいの時間が経過すべきかわからない場合に問題になる可能性があります。

flushと呼ばれる新しいユーティリティがAngular4.2で導入され、その問題を解決します。 マクロタスクキューが空になるまでの時間の経過をシミュレートします。 マクロタスクには、setTimoutssetIntervalsrequestAnimationFrameなどが含まれます。

したがって、flushを使用して、次のようなテストを作成できます。

src / app / app.component.spec.ts
import { TestBed, fakeAsync, flush } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ]
    }).compileComponents();
  });

  it('should increment in template', fakeAsync(() => {
    const fixture = TestBed.createComponent(AppComponent);

    fixture.debugElement
      .query(By.css('button.increment'))
      .triggerEventHandler('click', null);

    flush();
    fixture.detectChanges();

    const value = fixture.debugElement.query(By.css('h1')).nativeElement.innerText;
    expect(value).toEqual('1');
  }));
});

この時点で、テストを実行できます。

  1. ng test

これにより、'should increment in template'テスト結果が正常に生成されます。

結論

この記事では、サンプルテストでwaitForAsyncfakeAsyncを紹介しました。

詳細なAngularテストガイドについては、公式ドキュメントを参照することもできます。

モバイルバージョンを終了