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.
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 Observable
s 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.
Angular
(version 2+):it('retrieves all the cars', async(inject( [CarService], ( carService ) => {
carService.getCars().subscribe(result => expect(result.length).toBeGreaterThan(0));
}));
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));
});
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.
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(); }); });
AsyncTestCompleter
est obsolète https://github.com/angular/angular/issues/544 . injectAsync
l'a remplacé https://github.com/angular/angular/issues/4715#issuecomment-149288405mais injectAsync
est maintenant également obsolèteinjectAsync
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;
}) );