Testowanie elementu kątowego z błędem anulowania subskrypcji podczas czyszczenia komponentu

Testuję komponent, który subskrybuje params routera. Każda próba i wszystko działa dobrze. Ale jak zaglądam do konsoli to widzę błąd:

Błąd podczas czyszczenia aplikacji komponentu Viewcomponent lokalkonsole.(funkcja anonimowa) @ context.js: 232

Wiesz, dlaczego tak się dzieje?

Próbowałem usunąć unsubscribe() z metody ngOnDestroy() i błąd znika.

Czy karma / jaśmin wspiera unsubscribe() automatycznie?

Oto komponent i testy

Komponent

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

Serwis.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"         
      }
    ]);
  }
}
Author: BlackHoleGalaxy, 2017-04-11

10 answers

Komunikat o błędzie "Error during component cleanup" występuje, ponieważ gdy wywołane jest ngOnDestroy(), this.routeSubscription jest niezdefiniowane. Dzieje się tak, ponieważ ngOnInit() nigdy nie został wywołany, co oznacza, że nigdy nie subskrybowałeś trasy. Jak opisano w Angular Testing tutorial, komponent nie jest w pełni zainicjalizowany, dopóki nie wywołasz fixture.detectChanges() za pierwszym razem.

Dlatego poprawnym rozwiązaniem jest dodanie fixture.detectChanges() do bloku beforeEach() zaraz po wywołaniu createComponent. Można go dodać w dowolnym momencie po utworzeniu oprawa. W ten sposób komponent zostanie w pełni zainicjowany, w ten sposób oczyszczanie komponentu będzie również zachowywać się zgodnie z oczekiwaniami.

 83
Author: randomPoison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-10 14:38:37

Musisz refaktor swojej metody ngOnDestroy jak poniżej:

ngOnDestroy() {
  if ( this.routeSubscription)
    this.routeSubscription.unsubscribe();
}
 33
Author: musecz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-13 13:28:28

Więc Moja sytuacja była podobna, ale nie do końca taka sama: umieszczam to tutaj na wypadek, gdyby ktoś inny uznał to za pomocne. Podczas testów jednostkowych z Jamine / Karma otrzymywałem

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

Okazało się, że to dlatego, że nie obsługiwałem poprawnie moich obserwatorów i nie mieli na nich funkcji błędu. Więc poprawka dodawała funkcję błędu:

this.entityService.subscribe((items) => {
      ///Do work
},
  error => {
    this.errorEventBus.throw(error);
  });
 6
Author: David Brown,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-18 11:40:00

Jestem w podobnej sytuacji, w której chcę przetestować funkcję w moim komponencie poza kontekstem samego komponentu.

To mi się udało:

afterEach(() => {
  spyOn(component, 'ngOnDestroy').and.callFake(() => { });
  fixture.destroy();
});
 5
Author: Richard Medeiros,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-23 17:35:14

Dodanie do odpowiedzi @ David Brown poniższy kod działa dla mnie.

      .subscribe(res => {
          ...
        },
        error => Observable.throw(error)
      )
 3
Author: Petros Kyriakou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-12 12:46:55

W moim przypadku zniszczenie komponentu po każdym teście rozwiązało problem. Możesz więc spróbować dodać to do swojej funkcji opisującej:

afterEach(() => {
  fixture.destroy();
})
 3
Author: Alex Link,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-27 13:32:53

Musisz zrobić 2 rzeczy, aby rozwiązać ten błąd.

1 - dodaj oprawę.detectChanges (); in beforeEach ()
2 - musisz dodać poniżej, aby komponent mógł być jasny.

afterEach(() => {
        fixture.destroy();
      });
 2
Author: Manas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-17 04:38:04

Jak wyjaśnia @randomPoison, błąd jest wyzwalany, gdy komponent, który używa unsubscribe, nie jest inicjowany. Jednak wywołanie {[2] } jest rozwiązaniem, gdy błąd znajduje się w pliku spec danego komponentu.

Ale możemy również mieć do czynienia z FooComponent, który tworzy BarComponent i BarComponent używa unsubscribe w swoim ngOnDestroy. Trzeba zrobić porządne wyśmiewanie.

Sugerowałbym inne podejście do czyszczenia subskrypcji, takie, które jest deklaratywne i nie spowoduje takich problemów. Oto przykład:

export class BazComponent implements OnInit, OnDestroy {
  private unsubscribe$ = new Subject();

  ngOnInit(): void {
    someObservable$
      .pipe(takeUntil(this.unsubscribe$))
      .subscribe(...);
  }

  ngOnDestroy(): void {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}

Więcej o tym podejściu tutaj

 1
Author: Bogdan D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-17 15:50:34

W moim przypadku błąd był w szablonie. Wystąpił błąd w komponencie potomnym ngDestroy, który nie został zniszczony, ponieważ próbowałem ustawić właściwość readonly. Warto byłoby sprawdzić, czy komponenty dziecka są właściwie niszczone.

 0
Author: Vikhyath Maiya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-19 14:39:36

Dla mnie to, co naprawiło ten błąd, było wewnątrz ngOnDestroy mojego komponentu, zapakowałem wysyłkę do sklepu i wypisałem się z subskrypcji w spróbuj złapać.

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
Author: Papa_D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-12 15:43:56