webentwicklung-frage-antwort-db.com.de

Unit Test einer beobachtbaren in Angular 2

Was ist die richtige Methode zum Testen eines Dienstes, der ein Observable-Ergebnis in Angular 2 zurückgibt? Angenommen, wir haben eine getCars-Methode in einer CarService-Dienstklasse:

...
export class CarService{
    ...
    getCars():Observable<any>{
        return this.http.get("http://someurl/cars").map( res => res.json() );
    }
    ...
}

Wenn ich versuche, die Tests wie folgt zu schreiben, erhalte ich die Warnung: "SPEC HAS NO EXPECTATIONS":

it('retrieves all the cars', inject( [CarService], ( carService ) => {
     carService.getCars().subscribe( result => {         
         expect(result.length).toBeGreaterThan(0);
     } );       
}) );

Die Verwendung von injectAsync hilft nicht weiter, da es meines Erachtens mit Promise -Objekten funktioniert.

44
Erdinc Guzel

Abschließend möchte ich ein Arbeitsbeispiel nennen. Die Klasse Observable verfügt über eine Methode toPromise, die ein Observable-Objekt in ein Promise-Objekt konvertiert. Der richtige Weg sollte sein:

it('retrieves all the cars', injectAsync( [CarService], ( carService ) => {
  return carService.getCars().toPromise().then( (result) => {         
     expect(result.length).toBeGreaterThan(0);
  } );       
}) );

Aber während der obige Code mit jedem Observable-Objekt funktioniert, habe ich immer noch das Problem mit den Observables, die von HTTP-Anfragen zurückgegeben wurden, was wahrscheinlich ein Fehler ist. Hier ist ein Plunker, der den obigen Fall demonstriert: http://plnkr.co/edit/ak2qZH685QzTN6RoK71H?p=preview

Update:
Ab Version Beta.14 scheint es mit der mitgelieferten Lösung einwandfrei zu funktionieren.

9
Erdinc Guzel

Der richtige Weg für Angular (Ver. 2+):

it('retrieves all the cars', async(inject( [CarService], ( carService ) => {
     carService.getCars().subscribe(result => expect(result.length).toBeGreaterThan(0)); 
}));

Async Observables vs Sync Observables

Es ist wichtig zu verstehen, dass Observables entweder synchron oder asynchron sein können.

In Ihrem konkreten Beispiel ist die Observable asynchron (sie umschließt einen http-Aufruf).
Daher müssen Sie die Funktion async verwenden, die den Code in seinem Körper in einer speziellen asynchronen Testzone ausführt . Es fängt alle in seinem Körper erzeugten Versprechungen ab und verfolgt sie, sodass Testergebnisse nach Abschluss einer asynchronen Aktion erwartet werden können.

Wenn Ihre Observable jedoch eine synchrone war, zum Beispiel:

...
export class CarService{
    ...
    getCars():Observable<any>{
        return Observable.of(['car1', 'car2']);
    }
    ...

sie hätten die Funktion async nicht benötigt, und Ihr Test würde einfach werden

it('retrieves all the cars', inject( [CarService], ( carService ) => {
     carService.getCars().subscribe(result => expect(result.length).toBeGreaterThan(0)); 
});

Murmeln

Eine weitere Sache, die beim Testen von Observables im Allgemeinen und Angular im Besonderen zu beachten ist, ist Marmortest .

Ihr Beispiel ist ziemlich einfach, aber normalerweise ist die Logik komplexer als nur den http -Dienst aufzurufen, und das Testen dieser Logik bereitet Kopfzerbrechen.
Murmeln machen den Test sehr kurz, einfach und umfassend (besonders nützlich zum Testen ngrx-Effekte ).

Wenn Sie Jasmine verwenden, können Sie Jasminmurmeln verwenden, für Jest gibt es Scherzmurmeln , aber wenn Sie etwas bevorzugen ansonsten gibt es rxjs-marbles , das mit jedem Testframework kompatibel sein sollte.

Hier ist ein großartiges Beispiel für das Reproduzieren und Beheben eines Rennzustands mit Murmeln.


Offizieller Leitfaden zum Testen

54
JeB

https://angular.io/guide/testing zeigt derzeit einige Möglichkeiten. Hier ist eine:

it('#getObservableValue should return value from observable',
    (done: DoneFn) => {
       service.getObservableValue().subscribe(value => {
       expect(value).toBe('observable value');
       done();
    });
});
2
Marcus

AsyncTestCompleter ist veraltet https://github.com/angular/angular/issues/544 . injectAsync hat es ersetzt https://github.com/angular/angular/issues/4715#issuecomment-149288405
aber injectAsync ist jetzt auch veraltet
injectAsync ist nicht mehr veraltet https://github.com/angular/angular/pull/5721 (siehe auch Kommentar von @ErdincGuzel)

it('retrieves all the cars', injectAsync( [CarService], ( carService ) => {
     var c = PromiseWrapper.completer();
     carService.getCars().subscribe( result => {         
         expect(result.length).toBeGreaterThan(0);
         c.resolve();
     } ); 
     return c.promise;      
}) );
1