setTimeout 的 Angular2 异步测试问题

Angular2 async testing issue with setTimeout

我正在使用 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 管道,测试就会成功,否则就会失败。

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

testcomponent.html:

{{someDate | amTimeAgo}}

testcomponent.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() {
    }
}

testmodule.ts

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

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

testcomponent.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 中的所有 "left-over" 超时吗?或者我可以通过某种方式重置异步代码 运行 所在的区域来解决这个问题吗?

有其他人 运行 参与其中或知道如何成功测试吗?任何提示将不胜感激。

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

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

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 方法返回一个承诺。如果您忘记了承诺(或者只是 调用 方法 而没有 thening),那么测试将是同步的并且不需要 async。组件在测试完成后被销毁。测试通过。

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

     fixture.destroy();
    

    大家都很开心!

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


更新

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

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

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