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()
的解决方案后,我在实际测试中尝试了这个(此处的示例只是重现该问题所需的最少代码)。实际测试包含嵌套的承诺,否则我不会需要 async
和 detectChanges
方法。
虽然销毁建议很好并且有助于解决简单测试中的问题,但我的实际测试包含以下语句以确保正确解析嵌套的承诺:
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
(它删除了超时)永远不会被调用,并且超时被挂起,这让区域等待。
有几件事可以让它发挥作用:
我不知道您的组件中还有什么,但仅从您展示的内容来看,测试 不需要 使用async
。它这样做的唯一原因是因为您从 createComponent
方法返回一个承诺。如果您忘记了承诺(或者只是 调用 方法 而没有 thening),那么测试将是同步的并且不需要 async
。组件在测试完成后被销毁。测试通过。
不过这是更好的解决方案:只需自己销毁组件!
fixture.destroy();
大家都很开心!
我测试了这两种解决方案,它们都有效。
更新
因此,针对此特定情况商定的解决方案是模拟管道。管道不会影响组件的任何行为,因此我们不应该真正关心它的作用,因为它仅用于显示。管道本身已经由库的作者测试过,所以我们不需要在我们的组件中测试它的行为。
@Pipe({
name: 'amTimeAgo'
})
class MockTimeAgoPipe implements PipeTransform {
transform(date: Date) {
return date.toString();
}
}
然后只需要从TestBed
配置中取出MomentModule
,将MockTimeAgoPipe
添加到declarations
我正在使用 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()
的解决方案后,我在实际测试中尝试了这个(此处的示例只是重现该问题所需的最少代码)。实际测试包含嵌套的承诺,否则我不会需要 async
和 detectChanges
方法。
虽然销毁建议很好并且有助于解决简单测试中的问题,但我的实际测试包含以下语句以确保正确解析嵌套的承诺:
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
(它删除了超时)永远不会被调用,并且超时被挂起,这让区域等待。
有几件事可以让它发挥作用:
我不知道您的组件中还有什么,但仅从您展示的内容来看,测试 不需要 使用
async
。它这样做的唯一原因是因为您从createComponent
方法返回一个承诺。如果您忘记了承诺(或者只是 调用 方法 而没有 thening),那么测试将是同步的并且不需要async
。组件在测试完成后被销毁。测试通过。不过这是更好的解决方案:只需自己销毁组件!
fixture.destroy();
大家都很开心!
我测试了这两种解决方案,它们都有效。
更新
因此,针对此特定情况商定的解决方案是模拟管道。管道不会影响组件的任何行为,因此我们不应该真正关心它的作用,因为它仅用于显示。管道本身已经由库的作者测试过,所以我们不需要在我们的组件中测试它的行为。
@Pipe({
name: 'amTimeAgo'
})
class MockTimeAgoPipe implements PipeTransform {
transform(date: Date) {
return date.toString();
}
}
然后只需要从TestBed
配置中取出MomentModule
,将MockTimeAgoPipe
添加到declarations