Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Angular component with unsubscribe Error during cleanup of component

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"                }     ]);   } } 
like image 237
BlackHoleGalaxy Avatar asked Apr 11 '17 15:04

BlackHoleGalaxy


2 Answers

The "Error during component cleanup" error message happens because when ngOnDestroy() is called, this.routeSubscription is undefined. This happens because ngOnInit() 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 call fixture.detectChanges() the first time.

Therefore, the correct solution is to add fixture.detectChanges() to your beforeEach() block right after the createComponent 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.

like image 142
randomPoison Avatar answered Sep 24 '22 09:09

randomPoison


You need to refactor your method ngOnDestroy as below :

ngOnDestroy() {    if ( this.routeSubscription)      this.routeSubscription.unsubscribe();  }
like image 45
musecz Avatar answered Sep 23 '22 09:09

musecz