web-dev-qa-db-ja.com

Angular2-debounceTimeを使用した呼び出しのテスト

valueChangesdebounceTimeを使用して変更を検出するフォームコントロールを使用しています。 itemServiceメソッドが呼び出されているかどうかを確認するために、updateをスパイするテストを書いています。フォームコントロールからdebounceTimeを削除すると、テストは正常に機能します。

これがコンポーネントのフォームコントロールです。

_this.itemControl.valueChanges.debounceTime(300).subscribe(response => {
   this.itemService.update(response);
});
_

これがテストです

_it('should do stuff',
    inject([ItemService], (itemService) => {
      return new Promise((res, rej) =>{
        spyOn(itemService, 'update');
        let item = {
            test: 'test'
        };
        fixture.whenStable().then(() => {
          let itemControl = new FormControl('test');
          fixture.componentInstance.itemControl = itemControl;
          fixture.autoDetectChanges();

          fixture.componentInstance.saveItem(item);
          expect(itemService.update).toHaveBeenCalled();

})}));
_

これがコンポーネントのsaveItem関数です

_saveItem(item): void {
    this.itemControl.setValue(item);
}
_

私が言ったように、フォームコントロールからdebounceTimeを削除すると、テストは正常に実行されますが、それはできません。 expect呼び出しの前にtick()呼び出しを追加しようとしましたが、このエラーが発生します

_Unhandled Promise rejection: The code should be running in the fakeAsync zone to call this function ; Zone: ProxyZone ; Task: Promise.then ; Value: Error: The code should be running in the fakeAsync zone to call this function Error: The code should be running in the fakeAsync zone to call this function
_
11
avoliva

fakeAsync()およびtick()を使用する必要があります。問題のテストコードに基づいて、私の側で正常に実行された以下のコード(。spec.tsファイル)を確認してください。

以下のコードの説明:
fakeAsync()tick()は常に一緒に使用する必要があります。 async()/fixtureInstance.whenStable()を一緒に使用することはできますが、プログラマーの観点からは「予測可能」ではありません。できる限りfakeAsync()/tick()を使用することをお勧めします。テストコードがXHR呼び出し(別名テストHttpリクエスト)を行うときは、 のみasync()/fixtureInstance.whenStable()を使用する必要があります。

テストコードで非同期コードがどのように動作するかを手動で制御できるため、可能な場合はfakeAsync()/tick()を使用することをお勧めします。

以下のコード(.spec.tsファイル)でわかるように。設定したデバウンス値は_300_であったため、メソッドパラメータ_300_、tick(300)を指定してtickメソッドを呼び出すことが非常に重要です。仮にデバウンス値を_500_に設定した場合、この状況で合格させたいのであれば、テストコードではティック値を_500_にする必要があります。

tick(299)を設定するとテストが失敗することに気付くでしょうが、デバウンス値を_300_に設定しているので、それは正しいです。これは、fakeAsync()/tick()を使用する力を示しており、コードのタイミングを制御します(fakeAsync()/tick()を使用すると、マスターオブタイムになります)。


_// component.sandbox.spec.ts
import { async, TestBed, fakeAsync, tick, inject } from "@angular/core/testing";
import { ReactiveFormsModule } from "@angular/forms";
import { SandboxComponent } from "./component.sandbox";
import { ItemService } from "../../Providers";
import "rxjs/add/operator/debounceTime";

describe("testFormControl", () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ReactiveFormsModule],
      declarations: [SandboxComponent],
      providers: [ItemService],
    }).compileComponents();
  }));

  // The test you had questions about :)
  it("(fakeAsync usage) Should hit the ItemService instance's 'update' method once", fakeAsync(inject([ItemService], (itemService: ItemService) => {
    spyOn(itemService, "update");
    let fixture = TestBed.createComponent(SandboxComponent);
    fixture.detectChanges(); // It is best practices to call this after creating the component b/c we want to have a baseline rendered component (with ng2 change detection triggered) after we create the component and trigger all of its lifecycle events of which may cause the need for change detection to occur, in the case attempted template data bounding occurs.

    let componentUnderTest = fixture.componentInstance;

    componentUnderTest.saveItem("someValueIWantToSaveHEHEHE");

    tick(300); // avoliva :)

    expect(itemService.update).toHaveBeenCalled();

  })));

});
_

_// component.sandbox.ts
import { Component, OnInit } from "@angular/core";
import { FormGroup, FormControl } from "@angular/forms";
import { ItemService } from "../../Providers";

@Component({
  template: `
    <form [formGroup]="formGroupInstance">
      <input formControlName="testFormControl" />
      <button type="submit">Submit</button>
      <button type="button" (click)="saveItem(formGroupInstance.controls['testFormControl'].value)">saveItem(...)</button>
    </form>
  `,
  styleUrls: ["component.sandbox.scss"],
})
export class SandboxComponent extends OnInit {
  public formGroupInstance: FormGroup;
  public testFormControlInstance: FormControl;

  constructor(private itemService: ItemService) {
    super();

    this.testFormControlInstance = new FormControl();

    this.formGroupInstance = new FormGroup(
      {
        testFormControl: this.testFormControlInstance,
      },
    );
  }

  public ngOnInit() {
    this.testFormControlInstance.valueChanges
      .debounceTime(300) // avoliva
      .subscribe((formControlInstanceValue: {}) => {
        this.itemService.update(formControlInstanceValue);
      });
  }

  public saveItem(item: any) {
    this.testFormControlInstance.setValue(item);
  }

}
_

_// ../../Provider/index.ts
export class ItemService {
  public update(formControlInstanceValue: any) {
    // Makes http request to api to update item
    console.log(`HEY PROGRAMMER, YEAH YOU! :P \n => http request could have been made
    here to update an 'item' in the database.`);
  }
}
_
23
Steven Ov