valueChanges
とdebounceTime
を使用して変更を検出するフォームコントロールを使用しています。 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
_
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.`);
}
}
_