I am using Angular2.0.1 and was trying to write unit tests around an angular component with some async tasks. A fairly common thing to do, I'd say. Even their latest testing examples include these kind of async tests (see here).
My own test would never be successful, though, always failing with the message
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Long story short, it took me hours to pinpoint the actual source of problem. I am using the library angular2-moment and there I was using a pipe called amTimeAgo. This pipe contains a window.setTimeout(...), which is never removed. If I removed the amTimeAgo pipe, the tests succeed, otherwise they fail.
Here's some very bare bones code to reproduce the issue:
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);
});
}));
});
Does anyone have an idea how to successfully test this? Can I somehow kill all the "left-over" timeouts in an afterEach? Or can I somehow reset the zones the async code runs in to get rid of this issue?
Has anybody else run into this or know how to successfully test this? Any hints would be appreciated.
UPDATE:
After @peeskillet hinted at a solution using fixture.destroy()
, I went off and tried this in my actual tests (the examples here are just the minimum code required to reproduce the issue). The actual tests contain nested promises, otherwise I wouldn't have required the async
and detectChanges
approach.
While the destroy suggestion is great and helps with the problem in the simple tests, my actual tests contain the following statement to ensure that the nested promises are properly resolved:
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();
});
}));
The problem is that, with the amTimeAgo pipe in the page, the fixture.whenStable()
promise is never resolved, so my assertion code is never executed and the test still fails with the same timeout.
So even though the destroy suggestion works on the given simplified tests, it does not enable me to fix the actual tests.
Thanks
Ben