Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Test RxJS Observable.timer using typescript, karma and jasmine

Hi I'm relatively new to Angular2, Karma and Jasmine. Currently I'm using Angular 2 RC4 Jasmine 2.4.x I have an Angular 2 service which periodically calls an http service like this:

getDataFromDb() { return Observable.timer(0, 2000).flatMap(() => {
        return this.http.get(this.backendUrl)
            .map(this.extractData)
            .catch(this.handleError);
    });
}

Now I want to test the functionality. For testing purposes I have just tested the "http.get" on a separate function without the Observable.timer by doing:

const mockHttpProvider = {
    deps: [MockBackend, BaseRequestOptions],
    useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
        return new Http(backend, defaultOptions);
    }
}

describe('data.service test suite', () => {
    var dataFromDbExpected: any;

    beforeEachProviders(() => {
        return [
            DataService,
            MockBackend,
            BaseRequestOptions,
            provide(Http, mockHttpProvider),
        ];
    });

    it('http call to obtain data',
        inject(
            [DataService, MockBackend],
            fakeAsync((service: DataService, backend: MockBackend) => {
                backend.connections.subscribe((connection: MockConnection) => {
                    dataFromDbExpected =  'myData';
                    let mockResponseBody: any = 'myData';
                    let response = new ResponseOptions({ body: mockResponseBody });
                    connection.mockRespond(new Response(response));

                });
                const parsedData$ = service.getDataFromDb()
                    .subscribe(response => {
                        console.log(response);
                        expect(response).toEqual(dataFromDbExpected);
                    });
            })));
});

I obviously want to test the whole function with the Observable.timer. I think one might want to use the TestScheduler from the rxjs framework, but how can I tell to only repeat the timer function for x times? I couln't find any documentation using it in the typescript context.

Edit: I'm using rxjs 5 beta 6

Edit: Added working example for Angular 2.0.0 final release:

describe('when getData', () => {
    let backend: MockBackend;
    let service: MyService;
    let fakeData: MyData[];
    let response: Response;
    let scheduler: TestScheduler;

    beforeEach(inject([Http, XHRBackend], (http: Http, be: MockBackend) => {
        backend = be;
        service = new MyService(http);
        fakeData = [{myfake: 'data'}];
        let options = new ResponseOptions({ status: 200, body: fakeData });
        response = new Response(options);

        scheduler = new TestScheduler((a, b) => expect(a).toEqual(b));
        const originalTimer = Observable.timer;
        spyOn(Observable, 'timer').and.callFake(function (initialDelay, dueTime) {
            return originalTimer.call(this, initialDelay, dueTime, scheduler);
        });
    }));
    it('Should do myTest', async(inject([], () => {
        backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
        scheduler.schedule(() => {
            service.getMyData().subscribe(
                myData => {
                    expect(myData.length).toBe(3,
                        'should have expected ...');
                });
        }, 2000, null);
        scheduler.flush();
    })));
});
like image 409
stevehin Avatar asked Aug 26 '16 13:08

stevehin


2 Answers

You need to inject the TestScheduler into the timer method inside a beforeEach part:

beforeEach(function() {
  this.scheduler = new TestScheduler();
  this.scheduler.maxFrames = 5000; // Define the max timespan of the scheduler
  const originalTimer = Observable.timer;
  spyOn(Observable, 'timer').and.callFake(function(initialDelay, dueTime) {  
    return originalTimer.call(this, initialDelay, dueTime, this.scheduler);
  });
});

After that you have full control of the time with scheduleAbsolute:

this.scheduler.schedule(() => {
  // should have been called once
  // You can put your test code here
}, 1999, null);

this.scheduler.schedule(() => {
  // should have been called twice
  // You can put your test code here
}, 2000, null);

this.scheduler.schedule(() => {
  // should have been called three times
  // You can put your test code here
}, 4000, null);

this.scheduler.flush();

You need scheduler.flush() to start the TestScheduler.

edit: so if you want to only test it X times, use the schedule functions as often (and with the right absolute times in milliseconds) as you wish.

edit2: I added the missing scheduler start

edit3: I changed it so should be working with RxJs5

edit4: Add maxFrames setting since the default is 750ms and will prevent testing longer-running sequences.

like image 156
Simon Jentsch Avatar answered Sep 24 '22 23:09

Simon Jentsch


I had issues with the TestScheduler() approach because the schedule() arrow function would never execute, so I found another path.

The Observable.timer function just returns an Observable, so I created one from scratch to give me complete control.

First, create a var for the observer:

let timerObserver: Observer<any>;

Now in the beforeEach() create the spy and have it return an Observable. Inside the Observable, save your instance to the timer:

beforeEach(() => {
  spyOn(Observable, 'timer').and.returnValue(Observable.create(
    (observer => {
      timerObserver = observer;
    })
  ));
});

In the test, just trigger the Observable:

it('Some Test',()=>{
  // do stuff if needed

  // trigger the fake timer using the Observer reference
  timerObserver.next('');
  timerObserver.complete();

  expect(somethingToHappenAfterTimerCompletes).toHaveBeenCalled();
});
like image 21
JeffryHouser Avatar answered Sep 21 '22 23:09

JeffryHouser