69 votes

Test du composant angulaire avec désinscription Erreur lors du nettoyage du composant

Je vais tester un composant qui souscrivent routeur params. Tous les test et tout fonctionne bien. Mais si je regarde dans la console, je vois une erreur:

Erreur durant le nettoyage de l'élément de ApplicationViewComponent localConsole.(fonction anonyme) @ contexte.js:232

Savez-vous pourquoi cela se produit?

J'ai essayé de supprimer l' unsubscribe() de ngOnDestroy() méthode et l'erreur disparaît.

Est de karma/jasmin soutenant unsubscribe() automatiquement?

Ici est le composant et tests

Composant

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();
  }

}

Composant dans le fichier de spécification

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"         
      }
    ]);
  }
}

112voto

randomPoison Points 806

La solution retenue n'est pas optimal, il travaille dans le fait que le test n'est pas configuré correctement.

Le "Erreur lors de la composante de nettoyage" message d'erreur se produit car, lorsque ngOnDestroy() est appelé, this.routeSubscription n'est pas défini. Cela se produit parce que l' ngOnInit() n'a jamais été invoquée, ce qui signifie que vous n'avez jamais souscrit à la route. Comme décrit dans l' Angulaire de test tutoriel, le composant n'est pas initialisé entièrement jusqu'à ce que vous appelez fixture.detectChanges() la première fois.

Par conséquent, la bonne solution est d'ajouter fixture.detectChanges() votre beforeEach() bloc sur la droite après l' createComponent est appelé. Il peut être ajoutée à tout moment après la création de l'appareil. Cela permettra de s'assurer que le composant est complètement initialisé, de cette façon, composant de nettoyage également à se comporter comme prévu.

42voto

musecz Points 414

Vous devez refactoriser votre méthode ngOnDestroy comme ci-dessous:

 ngOnDestroy() {
  if ( this.routeSubscription)
    this.routeSubscription.unsubscribe();
} 

8voto

Richard Medeiros Points 593

Dans une situation similaire, je souhaite tester une fonction de mon composant en dehors du contexte du composant lui-même.

C'est ce qui a fonctionné pour moi:

 afterEach(() => {
  spyOn(component, 'ngOnDestroy').and.callFake(() => { });
  fixture.destroy();
});
 

7voto

David Brown Points 61

Donc, ma situation était similaire, mais pas tout à fait la même: je ne fais que mettre ceci ici au cas où quelqu'un d'autre le jugerait utile. Quand les tests unitaires avec Jamine / Karma, je devenais

  'ERROR: 'Error during cleanup of component','
 

Il s’avère que c’est parce que je n’ai pas correctement manipulé mes éléments observables et qu’ils ne comportaient pas de fonction d’erreur. Donc, le correctif ajoutait une fonction d'erreur:

 this.entityService.subscribe((items) => {
      ///Do work
},
  error => {
    this.errorEventBus.throw(error);
  });
 

3voto

Petros Kyriakou Points 2358

En ajoutant à la réponse de @David Brown, le code ci-dessous est ce qui a fonctionné pour moi.

       .subscribe(res => {
          ...
        },
        error => Observable.throw(error)
      )
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X