web-dev-qa-db-fra.com

Unité testant un observable dans Angular 2

Quelle est la bonne manière de tester par un unité un service renvoyant un résultat Observable sous la forme Angular 2?)? Supposons que nous ayons une méthode getCars dans une classe de service CarService:

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

Si j'essaie d'écrire les tests de la manière suivante, j'obtiens l'avertissement suivant: 'SPEC N'A PAS D'ATTENTES':

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

Utiliser injectAsync n'aide pas, car il fonctionne avec des objets Promise aussi loin que je pouvais le voir.

44
Erdinc Guzel

Enfin, je termine avec un exemple de travail. Observable class a une méthode toPromise qui convertit un observable en un objet Promise. La manière correcte devrait être:

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

Mais si le code ci-dessus fonctionne avec n’importe quel objet Observable, j’ai toujours le problème avec le Observables renvoyé par les requêtes HTTP, ce qui est probablement un bogue. Voici un dépliant démontrant le cas ci-dessus: http://plnkr.co/edit/ak2qZH685QzTN6RoK71H?p=preview

Mise à jour:
Depuis la version beta.14, il semble fonctionner correctement avec la solution fournie.

9
Erdinc Guzel

La manière correcte pour Angular (version 2+):

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

Async Observables vs Sync Observables

Il est important de comprendre que les observables peuvent être synchronisés ou asynchrones .

Dans votre exemple spécifique, l'observable est asynchrone (il encapsule un appel http).
Par conséquent, vous devez utiliser la fonction async qui exécute le code contenu dans son corps dans une zone de test asynchrone spéciale . Il intercepte et garde une trace de toutes les promesses créées dans son corps, ce qui permet d’attendre des résultats de test à la fin d’une action asynchrone.

Toutefois, si votre observable était un synchrone , par exemple:

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

vous n’auriez pas eu besoin de la fonction async et votre test deviendrait simplement

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

Marbres

Une autre chose à considérer lors du test des observables en général et des Angular en particulier est test de marbre .

Votre exemple est assez simple, mais la logique est généralement plus complexe que simplement appeler http service et que tester cette logique devient un casse-tête.
Les billes rendent le test très court, simple et complet (il est particulièrement utile pour tester effets ngrx ).

Si vous utilisez Jasmine, vous pouvez utiliser jasmine-marbles , pour Jest il y a jest-marbles , mais si vous préférez quelque chose sinon, il y a rxjs-marbles , ce qui devrait être compatible avec tout framework de test.

Here est un excellent exemple pour reproduire et réparer une situation de concurrence critique avec des billes.


Guide officiel de test

54
JeB

https://angular.io/guide/testing présente actuellement plusieurs méthodes. En voici un:

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

AsyncTestCompleter est obsolète https://github.com/angular/angular/issues/544 . injectAsync l'a remplacé https://github.com/angular/angular/issues/4715#issuecomment-149288405
mais injectAsync est maintenant également obsolète
injectAsync n'est plus obsolète https://github.com/angular/angular/pull/5721 (voir aussi le commentaire de @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
Günter Zöchbauer