javascript - Angular2 - 使用 debounceTime 测试调用

标签 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 测试执行正常,但我不能那样做。我尝试在 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(),但从程序员的 Angular 来看,它不太“可预测”。我建议您尽可能使用 fakeAsync()/tick()。你应该only当您的测试代码进行 XHR 调用(又名测试 Http 请求)时使用 async()/fixtureInstance.whenStable()

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

正如您在下面的代码(.spec.ts 文件)中看到的那样。你用方法参数300调用tick方法很重要,tick(300),因为你设置的debounce值是300。如果您假设将 debounce 值设置为 500,那么您的测试代码中的 tick 值应该是 500,如果您希望它在这种情况下通过。

您会注意到,如果您设置 tick(299),您的测试将失败,但这是正确的,因为您将 debounce 值设置为 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.`);
  }
}

关于javascript - Angular2 - 使用 debounceTime 测试调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41641995/

相关文章:

angular - 固定在 mat-table 中的第一列

typescript - Angular 2 @ViewChild 注释返回未定义

android - 模拟 Google Analytics v4

javascript - Google跟踪功能(i,s,o,g,r,a,m)代码如何工作?

javascript - 基本正则表达式问题

php - 在网页中单击按钮时,如何显示列表中的随机短语?

ruby - 通过 Test::Unit 查看异常消息是否与 Ruby 中的已知字符串匹配

c# - 如何使用最小起订量模拟 ConfigurationManager.AppSettings

node.js - Angular HttpClient 代理失败

javascript - 使用 javascript 进行简单语法高亮显示