Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing an observable in Angular 2

What is the correct way of unit testing a service returning an Observable result in Angular 2? Let's say we have a getCars method in a CarService service class:

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

If I try to write the tests in the following way I get the warning: 'SPEC HAS NO EXPECTATIONS':

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

Using injectAsync does not help because it works with Promise objects as far as I could see.

like image 429
Erdinc Guzel Avatar asked Jan 05 '16 09:01

Erdinc Guzel


People also ask

How do you write a test case for Observable in jest?

Testing a Single Emitted Value You can then write your expectations inside of the the subscribe callback, then call done() when you're ready for the test to finish. import { of } from 'rxjs'; test('the observable emits hello', done => { of('hello'). subscribe( data => { expect(data). toBe('hola'); done(); }); });

What is SpyOn in Angular unit testing?

Test the Component logic using SpyOn - Testing Angular. SpyOn is a Jasmine feature that allows dynamically intercepting the calls to a function and change its result. This example shows how spyOn works, even if we are still mocking up our service.

What is unit testing in Angular?

Angular Unit testing is the process of testing small and isolated pieces of code in your Angular application. This provides an added advantage to the users in the sense that they can add any new features without breaking any other part of their application.

Should constructor be unit tested?

If your constructor has, for example, an if (condition), you need to test both flows (true,false). If your constructor does some kind of job before setting. You should check the job is done.


1 Answers

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

like image 113
JeB Avatar answered Oct 13 '22 09:10

JeB