简体   繁体   English

Angular2 - 使用debounceTime测试调用

[英]Angular2 - Testing call with a debounceTime

I'm using a form control that detects changes using valueChanges and debounceTime . 我正在使用一个表单控件来检测使用valueChangesdebounceTime更改。 I'm writing a test that spies on itemService to check if the update method is being called. 我正在编写一个测试,它itemService以检查是否正在调用update方法。 If I remove the debounceTime from the form control the test works fine. 如果我从表单控件中删除debounceTime ,测试工作正常。

Here's the form control in the component. 这是组件中的表单控件。

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

Here's the 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();

})}));

Here's the component's saveItem function 这是组件的saveItem函数

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

Like I said, if I remove debounceTime from the form control the test executes fine, but I can't do that. 就像我说的,如果我从表单控件中删除debounceTime ,测试执行正常,但我不能这样做。 I've tried adding a tick() call before the expect call but I just get this error 我已经尝试在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

You should use fakeAsync() and tick() . 你应该使用fakeAsync()tick() Check out the code below (the .spec.ts file) that ran successfully on my end based on your test code in question. 查看下面代码 (.spec.ts文件),该代码根据您的测试代码成功运行。

Explanation of code below: 代码说明如下:
fakeAsync() and tick() should always be used together. fakeAsync()tick()应始终一起使用。 You can use async()/fixtureInstance.whenStable() together, but it is less "predictable" from a programmer's perspective. 您可以一起使用async()/fixtureInstance.whenStable() ,但从程序员的角度来看,它不那么“可预测”。 I would recommend you to use fakeAsync()/tick() whenever you can. 我建议你尽可能使用fakeAsync()/tick() You should only use async()/fixtureInstance.whenStable() when your test code makes an XHR call (aka testing Http request). 当测试代码进行XHR调用(也就是测试Http请求)时,您应该使用async()/fixtureInstance.whenStable() )。

It's best to use fakeAsync()/tick() when you can because you have manual control over how async code operate in your test code. 最好尽可能使用fakeAsync()/tick() ,因为您可以手动控制异步代码在测试代码中的运行方式。

As you can see in the code below (.spec.ts file). 正如您在下面的代码中看到的那样(.spec.ts文件)。 It is very important for you to call the tick method with the method parameter 300 , tick(300) , because the debounce value you set was 300 . 使用方法参数300tick(300)调用tick方法非常重要,因为您设置的去抖动值为300 If you hypothetically set your debounce value to 500 , then your tick value should be 500 in your testing code, if you want it to pass in this situation. 如果您假设将去抖动值设置为500 ,那么您的测试代码中的刻度值应为500 ,如果您希望它在这种情况下通过。

You will notice that if you set tick(299) your test will fail, but that is correct because you set your debounce value to 300 . 您会注意到,如果设置tick(299)您的测试将失败,但这是正确的,因为您将去抖动值设置为300 This shows you the power of using fakeAsync()/tick() , you control your codes timing (you are MASTER OF TIME, when you use fakeAsync()/tick() ). 这向您展示了使用fakeAsync()/tick()的强大功能,您可以控制代码的计时时间(当您使用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.`);
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM