Unit testing an observable in Angular 2
The correct way for Angular
(ver. 2+):
it('retrieves all the cars', waitForAsync(inject([CarService], (carService) => {
carService.getCars().subscribe(result => expect(result.length).toBeGreaterThan(0));
}));
Async Observables vs Sync Observables
It is important to understand that Observables can be either synchronous or asynchronous.
In your specific example the Observable is asynchronous (it wraps an http call).
Therefore you have to use waitForAsync
function that executes the code inside its body in a special async test zone. It intercepts and keeps track of all promises created in its body, making it possible to expect test results upon completion of an asynchronous action.
However, if your Observable was a synchronous one, for example:
...
export class CarService{
...
getCars():Observable<any>{
return Observable.of(['car1', 'car2']);
}
...
you wouldn't have needed waitForAsync
function and your test would become simply
it('retrieves all the cars', inject([CarService], (carService) => {
carService.getCars().subscribe(result => expect(result.length).toBeGreaterThan(0));
});
Marbles
Another thing to consider when testing Observables in general and Angular in particular is marble testing.
Your example is pretty simple, but usually the logic is more complex than just calling http
service and testing this logic becomes a headache.
Marbles make the test very short, simple and comprehensive (it is especially useful for testing ngrx effects).
If you're using Jasmine
you can use jasmine-marbles, for Jest
there is jest-marbles, but if you prefer something else, there is rxjs-marbles, that should be compatible with any test framework.
Here is a great example for reproducing and fixing a race condition with marbles.
Official guide for testing
https://angular.io/guide/testing currently shows a few ways. Here is one:
it('#getObservableValue should return value from observable', (done: DoneFn) => { service.getObservableValue().subscribe(value => { expect(value).toBe('observable value'); done(); }); });
Finally I end with a working example. Observable
class has a method toPromise that converts an Observable to a Promise object. The correct way should be:
it('retrieves all the cars', injectAsync( [CarService], ( carService ) => {
return carService.getCars().toPromise().then( (result) => {
expect(result.length).toBeGreaterThan(0);
} );
}) );
But while to above code works with any Observable object, I still have the problem with the Observable
s returned from Http requests which is probably a bug. Here is a plunker demonstrating the case above: http://plnkr.co/edit/ak2qZH685QzTN6RoK71H?p=preview
Update:
As of version beta.14 it seems to work properly with the provided solution.