setTimeout 的 Angular2 异步测试问题

标签 angular jasmine angular2-testing

我正在使用 Angular2.0.1 并尝试围绕带有一些异步任务的 Angular 组件编写单元测试。我会说这是一件相当普遍的事情。甚至他们最新的测试示例也包括此类异步测试(请参阅 here)。

不过,我自己的测试永远不会成功,总是失败并显示消息

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

长话短说,我花了几个小时才查明问题的真正根源。 我正在使用库 angular2-moment 并且在那里我使用了一个名为 amTimeAgo 的管道。此管道包含一个 window.setTimeout(...),它永远不会被删除。 如果我删除了 amTimeAgo 管道,测试就会成功,否则就会失败。

这里有一些非常简单的代码来重现这个问题:

测试组件.html:

{{someDate | amTimeAgo}}

测试组件.ts:

import { Component } from "@angular/core";
import * as moment from "moment";

@Component({
    moduleId: module.id,
    templateUrl: "testcomponent.html",
    providers: [],
})
export class TestComponent{
    someDate = moment();

    constructor() {
    }
}

测试模块.ts

import { NgModule }      from "@angular/core";
import {MomentModule} from 'angular2-moment';
import { TestComponent } from './testcomponent';

@NgModule({
    imports: [
        MomentModule,
    ],
    declarations: [
        TestComponent,
    ]
})
export class TestModule {
}

测试组件.spec.ts:

import { async, TestBed, ComponentFixture } from "@angular/core/testing";
import { TestComponent } from './testcomponent';
import { TestModule } from './testmodule';

let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;

function createComponent() {
    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;

    fixture.detectChanges();
    return Promise.resolve();
}

describe("TestComponent", () => {
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [
                TestModule],
        }).compileComponents();
    }));

    it('should load the TestComponent', async(() => {
        createComponent().then(() => {
            expect(component).not.toBe(null);            
        });
    }));

});

有没有人知道如何成功地测试这个?我能以某种方式杀死 afterEach 中的所有“剩余”超时吗?或者我可以通过某种方式重置运行异步代码的区域来解决这个问题吗?

有没有其他人遇到过这个问题或者知道如何成功测试这个问题?任何提示将不胜感激。

更新: 在 @peeskillet 暗示了使用 fixture.destroy() 的解决方案后,我开始在实际测试中尝试这个(这里的示例只是重现问题所需的最少代码)。实际测试包含嵌套的 promise ,否则我不会需要 asyncdetectChanges 方法。

虽然 destroy 建议很好并且有助于解决简单测试中的问题,但我的实际测试包含以下语句以确保正确解析嵌套的 promise:

it('should check values after nested promises resolved', async(() => {
    createComponent().then(() => {
        fixture.whenStable().then(() => {
            component.selectedToolAssemblyId = "2ABC100035";

            expect(component.selectedToolAssembly).toBeDefined();
            expect(component.selectedToolAssembly.id).toBe("2ABC100035");

            fixture.destroy();
        });
        fixture.detectChanges();
    });
}));

问题是,对于页面中的 amTimeAgo 管道,fixture.whenStable() promise 从未得到解决,因此我的断言代码从未被执行,测试仍然失败并出现相同的超时.

因此,即使销毁建议适用于给定的简化测试,它也无法让我修复实际测试。

谢谢

最佳答案

供引用: here is the problem pipe in question

我认为问题是当存在挂起的异步任务时,组件永远不会在 async 区域中被销毁,在本例中是管道的。因此,管道的 ngOnDestroy(删除超时)永远不会被调用,并且超时被挂起,这让区域等待。

有几件事可以让它发挥作用:

  1. 我不知道您的组件中还有什么,但仅从您展示的内容来看,测试不需要使用async。它这样做的唯一原因是因为您从 createComponent 方法返回一个 promise 。如果您忘记了 promise(或者只是调用方法没有thenning)那么测试将是同步的并且不需要async。组件在测试完成后被销毁。测试通过。

  2. 不过,这是更好的解决方案:只需自行销毁组件!

     fixture.destroy();
    

    大家都很开心!

我测试了这两种解决方案,它们都有效。


更新

因此,针对这种特殊情况商定的解决方案是模拟管道。管道不会影响组件的任何行为,因此我们不应该真正关心它的作用,因为它仅用于显示。管道本身已经由库的作者测试过,因此我们不需要在我们的组件中测试它的行为。

@Pipe({
  name: 'amTimeAgo'
})
class MockTimeAgoPipe implements PipeTransform {
  transform(date: Date) {
    return date.toString();
  }
}

然后只需从TestBed配置中取出MomentModule,并将MockTimeAgoPipe添加到declarations

关于setTimeout 的 Angular2 异步测试问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39867883/

相关文章:

使用 routerLink 的 Angular 2 单元测试组件

angular - 测试: blur emulate

angular - 启用无箭头的 mat-tab 滚动

unit-testing - 缓存如何解决?独立使用 jasmine 时出现问题

javascript - Jasmine 运行测试 3 次

Angular2 测试没有 LocationStrategy 的提供者

unit-testing - 单元测试 Angular 2 authGuard; spy 方法没有被调用

linux - 部署 Webpack Angular 2 应用

angularjs - Angular Quickstart 的 package.json 中 angular 前的 @ 符号

javascript - 无法在短时间内加载属性 - 导致错误