I have a http request of this form in my angular app: this.http.get(url,options).pipe(retry(5))
I want to unit test this using jasmine, such that the http request first returns an error but on a subsequent try, returns the expected data.
I previously returned http errors in this format as recommended by the angular.io documentation spyOn(http,'get').and.returnValue(defer(() => Promise.reject(errorObject)));
The rxjs operator retry does not seem to call the http.get function again, so changing the return value of the spy does not work. What I think I need to do is somehow return an observable from the spy which first emits the error and later emits the data. I thought about using a BehaviorSubject but don't think that accepts errors to be passed.
Is there any way of achieving this?
If we go to angular source code we will see next:
// Start with an Observable.of() the initial request, and run the handler (which
// includes all interceptors) inside a concatMap(). This way, the handler runs
// inside an Observable chain, which causes interceptors to be re-run on every
// subscription (this also makes retries re-run the handler, including interceptors).
const events$: Observable<HttpEvent<any>> =
of (req).pipe(concatMap((req: HttpRequest<any>) => this.handler.handle(req)));
To make request retriable they are starting it with of(req), so we could do the same to emulate this behavior in unit tests. For example:
spyOn(http,"get").and.returnValue(
createRetriableStream(
throwError("err"),
throwError("err"),
throwError("err"),
of("a")
)
);
Where createRetriableStream function could look like it:
function createRetriableStream(...resp$: any): any {
let fetchData: Spy = createSpy("fetchData");
fetchData.and.returnValues(...resp$);
return of(undefined).pipe(switchMap((_) => fetchData()));
}
Usually, retry logic is used together with a delay between attempt, to cover this part you could use time progression syntax. So your tests could look like:
it("throw error after 2 retries", () => {
testScheduler.run(({ cold, expectObservable }) => {
source$ = createRetriableStream(cold("-#"), cold("-#"), cold("-#"),cold("-3")).pipe(
retryWhen(
return errors.pipe(
mergeMap((err) => {
return 2 > repeatCount
? throwError(err)
: timer(100, testScheduler);
})
);)
);
expectObservable(source$).toBe("- 200ms --#");
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With