javascript - Angular2 - debounceTime ile çağrıyı test etme
Bir form kontrolü kullanıyorum ve değişiklikleri valueChanges ve debounceTime ile algılıyor. itemService üzerinde update metodunun çağrılıp çağrılmadığını kontrol etmek için bir test yazıyorum. Form kontrolünden debounceTime'ı kaldırırsam test sorunsuz çalışıyor.
İşte bileşendeki form kontrolü.
this.itemControl.valueChanges.debounceTime(300).subscribe(response => {
this.itemService.update(response);
});
İşte test
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();
})}));
İşte bileşenin saveItem fonksiyonu
saveItem(item): void {
this.itemControl.setValue(item);
}
Dediğim gibi, form kontrolünden debounceTime öğesini kaldırırsam test düzgün çalışıyor, ama bunu yapamam. expect çağrısından önce bir tick() çağrısı eklemeyi denedim ama sadece bu hatayı alıyorum
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
Cevap #1
fakeAsync() ve tick() kullanmalısınız. Aşağıdaki koda ( .spec.ts dosyası) göz atın; bu, sorunuzdaki test kodunu temel alarak benim tarafımda başarıyla çalıştı.
Aşağıdaki kodun açıklaması:
fakeAsync() ve tick() her zaman birlikte kullanılmalıdır. async()/fixtureInstance.whenStable() birlikte kullanılabilir, ancak bir programcı açısından daha az "öngörülebilirdir". Mümkün olduğunda fakeAsync()/tick() kullanmanızı tavsiye ederim. Test kodunuz bir XHR çağrısı yaptığında (yani Http isteğini test ederken) yalnızca async()/fixtureInstance.whenStable() kullanmalısınız.
Test kodunuzda asenkron kodların nasıl çalıştığı üzerinde manuel kontrole sahip olduğunuz için, mümkün olduğunda fakeAsync()/tick() kullanmak en iyisidir.
Aşağıdaki kodda (.spec.ts dosyası) görebileceğiniz gibi, tick metodunu 300 parametresi ile, tick(300) şeklinde çağırmanız sizin için çok önemlidir, çünkü ayarladığınız debounce değeri 300 idi. Eğer hipotetik olarak debounce değerinizi 500 olarak ayarlasaydınız, test kodunuzda tick değeri de 500 olmalıydı, eğer bu durumda testin geçmesini istiyorsanız.
Şunu fark edeceksiniz ki eğer tick(299) olarak ayarlarsanız testiniz başarısız olacak, ama bu doğru çünkü debounce değerini 300 olarak ayarladınız. Bu, fakeAsync()/tick() kullanmanın gücünü gösteriyor, kod zamanlamasını siz kontrol ediyorsunuz (siz zamana HAKİM oluyorsunuz, fakeAsync()/tick() kullandığınızda).
// 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.`);
}
}
