I'm quite new to unit testing observables, so far I found out that there are two ways of testing them either by using the subscribe and assert pattern or by using marble testing.
I do now have a task at hand where I need some sort of long polling implementation. The implementation is working fine, but writing the according unit test is a pain.
Here is the code I have so far:
long-polling.service.ts
// ...
/**
* Starts polling a long polling endpoint. Pass in any kind of observable stream
* to the method and eg. from the httpClient.get(). You can also configure a maximum
* number of retries in case the server might fail and the retry interval in ms.
* @param observableToPoll$
* @param maxRetrys
* @param retryIntervalOnError
*/
startPolling<T>(
observableToPoll$: Observable<T>,
maxRetrys: number = Infinity,
retryIntervalOnError = 5000
): Observable<T> {
if (this.poll$) {
return this.poll$;
}
this.poll$ = observableToPoll$.pipe(
longPollUntil(this.stopPolling$, maxRetrys, retryIntervalOnError)
);
return this.poll$;
}
//...
util.ts
export function longPollUntil<T>(
isPollingActive: IsActiveCallback | Observable<any>,
maxRetrys = Infinity,
retryInterval = 5000
): MonoTypeOperatorFunction<T> {
return (source$) => {
const poll$ = source$.pipe(
repeat(),
retryWhen((errors) =>
errors.pipe(
concatMap((e, i) =>
iif(
() => i >= maxRetrys,
throwError(e),
of(e).pipe(delay(retryInterval))
)
)
)
),
share()
);
if (isObservable(isPollingActive)) {
return poll$.pipe(takeUntil(isPollingActive));
}
return poll$.pipe(takeWhile(isPollingActive as (res: T) => boolean, true));
};
}
long-polling.service.spec.ts
// ...
it('should start polling and repeat after stream ends', fakeAsync((done) => {
const httpClient = TestBed.inject(HttpClient);
const polling = service.startPolling(
httpClient.get('/sometest/test.json').pipe(
// observeOn(scheduler),
tap(() => {
console.log('request fired');
})
)
);
let callCount = 0;
polling.subscribe((data: any) => {
if (callCount === 0) {
expect(data.someMessage).toBe('Test message');
} else {
expect(data.someMessage).toBe('Another message');
}
callCount++;
if (callCount > 2) {
done();
}
}, done.fail);
const req = httpMock.match('/sometest/test.json');
expect(req[0].request.method).toBe('GET');
scheduler.flush();
req[0].flush({
someMessage: 'Test message',
});
scheduler.flush();
//tick(70000);
req[1].flush({
someMessage: 'Another message',
});
}));
// ...
As you can see I tried several approaches already. But the request is only fired once and does not repeat like it should do.
Does anyone have a clue how to test this?