Angular/Karma 单元测试错误“1 个计时器仍在队列中”

Posted

技术标签:

【中文标题】Angular/Karma 单元测试错误“1 个计时器仍在队列中”【英文标题】:Angular/Karma unit test error "1 timer(s) still in the queue" 【发布时间】:2020-02-13 10:49:10 【问题描述】:

这不是我第一次遇到"1 timer(s) still in the queue",但通常我会找到一些使用tick()detectChanges() 等的方法来摆脱它。

在我尝试测试我知道应该引发异常的条件之前,下面的测试工作正常:

  it('should be able to change case', fakeAsync(() => 
    expect(component).toBeTruthy();

    fixture.whenStable().then(fakeAsync(() => 
      component.case = 'lower';
      fixture.autoDetectChanges();
      tick(500);
      const input = fixture.nativeElement.querySelector('input') as htmlInputElement;
      typeInElement('abcDEF', input);
      fixture.autoDetectChanges();
      tick(500);
      expect(component.text).toEqual('abcdef');

      component.case = 'upper';
      fixture.autoDetectChanges();
      tick(500);
      typeInElement('abcDEF', input);
      fixture.autoDetectChanges();
      tick(500);
      expect(component.text).toEqual('ABCDEF');

      // Everything above works fine. Here's where the trouble begins
      expect(() => 
        component.case = 'foo';
        fixture.autoDetectChanges();
        tick(500);
      ).toThrowError(/Invalid case attribute/);
    ));
  ));

我正在测试的是一个 Angular 组件,它是 Material 输入字段的包装器。该组件有许多可选属性,其中大多数只是用于常见输入字段功能的传递属性,但也有一些自定义属性,例如我在上面测试的用于大写/小写转换的属性。

case 属性的可接受值为upperlowermixed(将空字符串、null 或未定义视为mixed)。组件应该为其他任何事情抛出异常。显然它确实如此,并且测试成功了,但是随着我获得的成功:

ERROR: 'Unhandled Promise rejection:', '1 timer(s) still in the queue.', '; Zone:', 'ProxyZone', '; Task:', 'Promise.then', '; Value:', Error: 1 timer(s) still in the queue.
Error: 1 timer(s) still in the queue.
   ...

谁能告诉我我可能做错了什么,或者是清除挥之不去的计时器的好方法?

免责声明:当我寻求 Karma 单元测试的帮助时,一个大问题是,即使我明确搜索“业力”,我也大多会找到 Pr0tractor、Pr0tractor 和更多 Pr0tractor 的答案。这不是 Pr0tractor! (故意拼错了一个零,所以它不会得到搜索匹配。)

更新:我可以像这样解决我的问题:

      expect(() => 
        component.inputComp.case = 'foo';
      ).toThrowError(/Invalid camp-input case attribute/);

这不如通过测试组件模板中的 HTML 属性分配(错误的)值来进行测试,因为我只是将值直接强制到属性本身的组件设置器中,但它'直到我有更好的解决方案。

【问题讨论】:

好的,除了测试问题,为什么不将属性定义为枚举或允许字符串的联合,这样可以在编译时捕获? 实际上,它是这样定义的 (type InputCase = 'lower' | 'mixed' | 'upper';),但是通过 HTML 分配的属性并不能可靠地得到类型检查。 顺便问一下,您对我的免责声明有什么问题? 另外,通过使用检查有效性的设置器,该值也可以不区分大小写。 这是 Angular 的一个非常重要的功能(而不是错误功能),您可以创建自定义组件,然后让用户按照自己的意愿使用这些组件在 HTML 中,例如<my-input [(ngModel)]="partNumber" case="upper">。至于引发异常与通过控制台警告悄悄失败,这两种方法都有优缺点,在这种情况下,无论如何这不是我的决定。 【参考方案1】:

我最近遇到了同样的问题 - 为了解决我在 it 函数结束时从 @angular/core/testing 调用了 discardPeriodicTasks() 并且在那之后我的测试通过了。

在这种情况下,您可能希望在最终的 expect 之前插入它

 it('should be able to change case', fakeAsync(() => 
    expect(component).toBeTruthy();

    fixture.whenStable().then(fakeAsync(() => 
      component.case = 'lower';
      fixture.autoDetectChanges();
      tick(500);
      const input = fixture.nativeElement.querySelector('input') as HTMLInputElement;
      typeInElement('abcDEF', input);
      fixture.autoDetectChanges();
      tick(500);
      expect(component.text).toEqual('abcdef');

      component.case = 'upper';
      fixture.autoDetectChanges();
      tick(500);
      typeInElement('abcDEF', input);
      fixture.autoDetectChanges();
      tick(500);
      expect(component.text).toEqual('ABCDEF');

      discardPeriodicTasks() <-------------------- try here

      // Everything above works fine. Here's where the trouble begins
      expect(() => 
        component.case = 'foo';
        fixture.autoDetectChanges();
        tick(500);
      ).toThrowError(/Invalid case attribute/);
      
    ));

tick 在你的 fakeAsync 上下文中移动时间。

flush 通过排空宏任务队列直到它为空来模拟该上下文中时间的完成。

discardPeriodicTasks“抛出”任何剩余的周期性任务。

它们各自用于不同的目的,并且会有不同的用例。

【讨论】:

我已经从不久前遇到此问题的项目中移出,但我应该将此答案添加为书签,以便下次我重新进行更多 Angular 单元测试时尝试一下。 今天早上真的让我省了很多麻烦,所以我想分享一下。最佳 flush() 作为单元测试中的最后一条指令为我工作。不幸的是,discardPeriodicTasks() 没有效果:-)【参考方案2】:

我也遇到过类似的问题。解决方案是flush函数用法。

import  fakeAsync, flush  from '@angular/core/testing';

it('test something', fakeAsync(() => 

  // ...

  flush();
));

【讨论】:

仅供参考:在处理按钮点击并要求考虑其副作用时,我偶然发现了 fakeAsync 的合法用例!在“异步”块等效项中使用 tick() 的要求是 setTimeout(()=>,0) 但与使用 fixture.whenStable() 一起使用更多的代码膨胀..

以上是关于Angular/Karma 单元测试错误“1 个计时器仍在队列中”的主要内容,如果未能解决你的问题,请参考以下文章

Angular - Jasmine/karma - 订阅 lambda 表达式未执行

Angular Karma Jasmine 错误:非法状态:无法加载指令摘要

未捕获的类型错误:无法读取未定义抛出的属性“coSearchCriteria” - Angular Karma/Jasmine

Angular5 / Karma'选择器'不是已知元素[重复]

Angular Karma - nb-card-header' 不是已知元素

Angular Karma - TypeError:无法读取未定义的属性“_id”