使用 debounce 测试角度指令

Posted

技术标签:

【中文标题】使用 debounce 测试角度指令【英文标题】:Testing angular directive with debounce 【发布时间】:2019-08-16 21:25:08 【问题描述】:

我为我的搜索字段编写了一个 debounce 指令,以防止过多的后端调用/处理。它按预期工作。但是我无法为它编写规范。我希望你们中的一些人可以帮助我:) 如果你想尝试它,我创建了一个 plunker here。目前我认为该指令未按预期初始化。

我的指令

import  NgModel  from '@angular/forms';
import  fromEvent  from 'rxjs';
import  debounceTime, map  from 'rxjs/operators';

@Directive(selector: '[debounce]')
export class DebounceDirective implements OnInit 
  @Input() delay: number = 300;

  constructor(private elementRef: ElementRef, private model: NgModel) 
  

  public ngOnInit(): void 
    const eventStream = fromEvent(this.elementRef.nativeElement, 'keyup').pipe(
      map(() => 
        return this.model.value;
      ),
      debounceTime(this.delay));

    this.model.viewToModelUpdate = () => 
    ;

    eventStream.subscribe(input => 
      this.model.viewModel = input;
      this.model.update.emit(input);
    );
  

我的规格

import  DebounceDirective  from '@modules/shared/directives/debounce.directive';
import  ComponentFixture, fakeAsync, TestBed, tick  from '@angular/core/testing';
import  Component, DebugElement, ViewChild  from '@angular/core';
import  By  from '@angular/platform-browser';
import  FormsModule  from '@angular/forms';

@Component(
  template: '<input type="text" name="test" debounce [delay]="500" [(ngModel)]="value" (ngModelChange)="test()">'
)
class DebounceDirectiveTestingComponent 
  @ViewChild(DebounceDirective) directive: DebounceDirective;
  public value: string = '';

  public test() 
  


describe('Directive: Debounce', () => 
  let component: DebounceDirectiveTestingComponent;
  let fixture: ComponentFixture<DebounceDirectiveTestingComponent>;
  let inputEl: DebugElement;

  beforeEach(() => 
    TestBed.configureTestingModule(
      imports: [FormsModule],
      declarations: [DebounceDirective, DebounceDirectiveTestingComponent]
    ).compileComponents();
  );

  beforeEach(() => 
    fixture = TestBed.createComponent(DebounceDirectiveTestingComponent);
    component = fixture.componentInstance;
    inputEl = fixture.debugElement.query(By.css('input'));
    fixture.detectChanges();
  );

  it('should create component', () => 
    expect(component).toBeTruthy();
  );

  it('should emit values after 500 msec debounce', fakeAsync(() => 
    const directiveEl = fixture.debugElement.query(By.directive(DebounceDirective));
    spyOn(component, 'test').and.stub();
    spyOn(component.directive, 'ngOnInit').and.callThrough();

    fixture.detectChanges();

    expect(directiveEl).toBeDefined();
    expect(component.directive.ngOnInit).toHaveBeenCalled();
    expect(component.directive.delay).toBe(500);

    inputEl.nativeElement.value = 'test';
    inputEl.nativeElement.dispatchEvent(new Event('keyup'));

    fixture.detectChanges();

    expect(component.value).toBe('');

    tick(500);
    fixture.detectChanges();

    expect(component.test).toHaveBeenCalled();
    expect(component.value).toBe('test');
  ));
);

【问题讨论】:

关于 onInit 间谍的事情是,onInit 被第一个 detectChanges 调用触发。因此,在您的测试中,您在实际监视它之前触发 onInit。如果你删除了你的 beforeEach 中的 fixture.detectChanges ,而是将它添加到你的第一个测试用例中,而另一个保持原样,你的 onInit 就会被调用。但是,价值仍然存在问题。我也看看能不能找到解决办法。 不是答案,但无论如何都很有趣 - 正如@Erbsenkoenig 指出的那样,注释了对fixture.detectChanges() 的第一次调用,同时还将事件类型更改为“输入”而不是“keyup” .spec 和指令都通过了测试 - 请参阅 StackBlitz 的这个分支。但是,从您的代码中,我看到您想要消除每个按键的抖动,因此我不提供此解决方案。看起来this.model.value 没有通过keyup 事件正确设置... 我还在你的指令中的 map 之前放了一个水龙头,以表明无论你使用 'input' 事件还是 'keyup' 事件,发送到可观察管道的事件在event.target.value. 大家好,感谢您的帮助!我将切换到输入事件,因为我的用例没有区别。现在它工作正常。你们中的某个人应该写一个明确的答案,我会给你代表。 Keyup 事件似乎是主要问题,因为它在发出值之前触发 (***.com/questions/38502560/…) 【参考方案1】:

正如@trollr 所建议的,我发布了作为答案的解决方案:将事件类型从“keyup”事件更改为“input”事件。

在指令中:

const eventStream = fromEvent(this.elementRef.nativeElement, 'input').pipe(

在测试中:

inputEl.nativeElement.dispatchEvent(new Event('input'));

这些更改以及注释掉对fixture.detectChanges() 的第一次调用已经解决了这个问题。

这是StackBlitz,显示所有测试通过。

【讨论】:

以上是关于使用 debounce 测试角度指令的主要内容,如果未能解决你的问题,请参考以下文章

vue防抖节流之v-debounce--throttle使用指南

bind, debounce 突然报错 Expect a function 以及debounce 不生效

Lodash 之 debounce

在 debounce 函数中使用 requestAnimationFrame 是个好主意吗?

如何在异步功能上使用 debounce? [复制]

将 debouncer 与 React 事件一起使用