Angular2 - 使用debounceTime测试调用

时间:2017-01-13 19:21:45

标签: javascript unit-testing angular typescript

我正在使用表单控件来检测使用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,测试执行正常,但我无法做到。我在tick()来电之前尝试添加expect来电,但我收到了此错误

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

1 个答案:

答案 0 :(得分:19)

您应该使用 fakeAsync() tick()。查看根据相关测试代码在我的终端成功运行的以下代码(.spec.ts文件)。

以下代码说明:
fakeAsync()tick()应始终一起使用。您可以一起使用async()/fixtureInstance.whenStable(),但从程序员的角度来看,它不那么“可预测”。我建议您尽可能使用fakeAsync()/tick()。当您的测试代码进行XHR调用(也就是测试Http请求)时,您应only使用async()/fixtureInstance.whenStable()

最好尽可能使用fakeAsync()/tick(),因为您可以手动控制异步代码在测试代码中的运行方式。

正如您在下面的代码中看到的那样(.spec.ts文件)。使用方法参数300tick(300)调用tick方法非常重要,因为您设置的去抖动值为300。如果您假设将去抖动值设置为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.`);
  }
}