web-dev-qa-db-fra.com

Test Angular angulaire avec erreur de désabonnement lors du nettoyage du composant

Je teste un composant qui souscrit des paramètres de routeur. Chaque test passe et tout fonctionne bien. Mais si je regarde dans la console, je peux voir une erreur:

Erreur lors du nettoyage du composant ApplicationViewComponent localConsole. (Fonction anonyme) @ context.js: 232

Savez-vous pourquoi cela se produit?

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

Le karma/jasmin prend-il en charge unsubscribe() automatiquement?

Voici le composant et les 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();
  }

}

Fichier de spécification de composant

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

La solution acceptée n'est pas optimale, elle contourne le fait que le test n'est pas configuré correctement.

Le message d'erreur "Erreur lors du nettoyage du composant" se produit car, lorsque ngOnDestroy() est appelé, this.routeSubscription N'est pas défini. Cela est dû au fait que ngOnInit() n'a jamais été appelé, ce qui signifie que vous ne vous êtes jamais abonné à la route. Comme décrit dans le tutoriel de tests angulaires , le composant n'est pas complètement initialisé tant que vous n'avez pas appelé fixture.detectChanges() pour la première fois.

Par conséquent, la solution correcte consiste à ajouter fixture.detectChanges() à votre bloc beforeEach() juste après l'appel de createComponent. Il peut être ajouté à tout moment après avoir créé le projecteur. Cela garantira que le composant est complètement initialisé, ainsi son nettoyage se déroulera également comme prévu.

69
randomPoison

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

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

Donc, ma situation était similaire, mais pas tout à fait la même: je mets ceci simplement 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);
  });
6
David Brown

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)
      )
3
Petros Kyriakou

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

Vous devez faire 2 choses pour résoudre cette erreur.

1- ajouter fixture.detectChanges (); dans beforeEach ()
2 - vous devez ajouter ce qui suit pour que le composant soit clair.

afterEach(() => {
        fixture.destroy();
      });
1
Manas

Dans mon cas, la destruction du composant après chaque test a résolu le problème. Vous pouvez donc essayer d'ajouter ceci à votre fonction de description:

afterEach(() => {
  fixture.destroy();
})
1
Alex Link

Eh bien dans mon cas, l'erreur était dans le modèle. Il y avait une erreur dans le composant enfant ngDestroy, qui n'a pas été détruit car j'essayais de définir la propriété readonly. Cela vaudrait la peine de vérifier si les composants de votre enfant sont correctement détruits.

0
Vikhyath Maiya

Pour moi, ce qui a corrigé cette erreur à l’intérieur de ngOnDestroy de mon composant, j’enveloppais la répartition de mon magasin et ma désinscription dans une capture d’essai.

ngOnDestroy(): void {
 try {
  this.store.dispatch(new foo.Bar(this.testThing()));
  if(this.fooBarSubscription) {
   this.fooBarSubscription.unsubscribe();
  }
 } catch (error) {
   this.store.dispatch(new foo.Bar(this.testThing()));
  }
}
0
Papa_D