I'm testing a component which subscribe router params. Every test pass and everything works fine. But if I look in the console, I can see an error:
Error during cleanup of component ApplicationViewComponent localConsole.(anonymous function) @ context.js:232
Do you know why this occurs?
I tried removing the unsubscribe()
from ngOnDestroy()
method and the error disappears.
Is karma/jasmine supporting unsubscribe()
automatically?
Here is the component and tests
Component
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Rx'
import { AppService } from 'app.service';
@Component({
selector: 'app-component',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
private routeSubscription: Subscription;
// Main ID
public applicationId: string;
constructor(
private route: ActivatedRoute,
private _service: AppService
) { }
ngOnInit() {
this.routeSubscription = this.route.params.subscribe(params => {
this.applicationId = params['id'];
this.getDetails();
this.getList();
});
}
getDetails() {
this._service.getDetails(this.applicationId).subscribe(
result => {
console.log(result);
},
error => {
console.error(error);
},
() => {
console.info('complete');
}
);
}
getList(notifyWhenComplete = false) {
this._service.getList(this.applicationId).subscribe(
result => {
console.log(result);
},
error => {
console.error(error);
},
() => {
console.info('complete');
}
);
}
ngOnDestroy() {
this.routeSubscription.unsubscribe();
}
}
Component spec file
import { NO_ERRORS_SCHEMA } from '@angular/core';
import {
async,
fakeAsync,
ComponentFixture,
TestBed,
tick,
inject
} from '@angular/core/testing';
import {
RouterTestingModule
} from '@angular/router/testing';
import {
HttpModule
} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Router, ActivatedRoute } from '@angular/router';
// Components
import { AppComponent } from './app.component';
// Service
import { AppService } from 'app.service';
import { AppServiceStub } from './app.service.stub';
let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let service: AppService;
let expectedApplicationId = 'abc123';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AppComponent],
imports: [RouterTestingModule, HttpModule],
providers: [
FormBuilder,
{
provide: ActivatedRoute,
useValue: {
params: Observable.of({id: expectedApplicationId})
}
},
{
provide: AppService,
useClass: AppServiceStub
}
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents();
}));
tests();
});
function tests() {
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;
service = TestBed.get(AppService);
});
/*
* COMPONENT BEFORE INIT
*/
it(`should be initialized`, () => {
expect(fixture).toBeDefined();
expect(comp).toBeDefined();
});
/*
* COMPONENT INIT
*/
it(`should retrieve param id from ActivatedRoute`, async(() => {
fixture.detectChanges();
expect(comp.applicationId).toEqual(expectedApplicationId);
}));
it(`should get the details after ngOnInit`, async(() => {
spyOn(comp, 'getDetails');
fixture.detectChanges();
expect(comp.getDetails).toHaveBeenCalled();
}));
it(`should get the list after ngOnInit`, async(() => {
spyOn(comp, 'getList');
fixture.detectChanges();
expect(comp.getList).toHaveBeenCalled();
}));
}
service.stub
import { Observable } from 'rxjs/Observable';
export class AppServiceStub {
getList(id: string) {
return Observable.from([
{
id: "7a0c6610-f59b-4cd7-b649-1ea3cf72347f",
name: "item 1"
},
{
id: "f0354c29-810e-43d8-8083-0712d1c412a3",
name: "item 2"
},
{
id: "2494f506-009a-4af8-8ca5-f6e6ba1824cb",
name: "item 3"
}
]);
}
getDetails(id: string) {
return Observable.from([
{
id: id,
name: "detailed item 1"
}
]);
}
}
I'm in a similar situation where I want to test a function in my component outside the context of the component itself.
This is what worked for me:
The accepted solution isn't optimal, it works around the fact that the test isn't setup correctly.
The "Error during component cleanup" error message happens because when
ngOnDestroy()
is called,this.routeSubscription
is undefined. This happens becausengOnInit()
was never invoked, meaning that you never subscribed to the route. As described in the Angular testing tutorial, the component isn't initialized fully until you callfixture.detectChanges()
the first time.Therefore, the correct solution is to add
fixture.detectChanges()
to yourbeforeEach()
block right after thecreateComponent
is called. It can be added any time after you create the fixture. Doing so will ensure that the component is fully initialized, that way component cleanup will also behave as expected.You need to refactor your method ngOnDestroy as below :
Adding to @David Brown's response the code below is what worked for me.
You have to do 2 things, to solve this error.
1- add fixture.detectChanges(); in beforeEach()
2 - you need to add below, so that component can be clear.
For me what fixed this error was inside of my component's ngOnDestroy, I wrapped my store dispatch and my unsubscribe in a try catch.