Reputation: 115
I'm struggling on how to test the retryWhen
operator in a redux-observable epic
. Based on this example taken from docs, I forked this jsbin where I'm trying to test the case where the response fails 2 times and after that it returns a valid response.
Below are some part of the code. For the whole implementation please use this jsbin
let RetryStrategy = attempts => attempts
.zip(Observable.range(1, 4))
.flatMap(([error, i]) => {
if (i > 3) {
return Observable.throw('Network error occured')
}
return Observable.timer(i * 1000)
})
const fetchFooEpic = (action$, store, call = indirect.call) =>
action$.ofType('FETCH_FOO')
.mergeMap(action =>
call(api.fetchFoo, action.payload.id)
.map(payload => ({ type: 'FETCH_FOO_FULFILLED', payload }))
.retryWhen(RetryStrategy)
.takeUntil(action$.ofType('FETCH_FOO_CANCELLED'))
.catch(error => of({
type: 'FETCH_FOO_REJECTED',
payload: error.xhr.response,
error: true
}))
);
describe('fetchFooEpic', () => {
...
it.only('handles errors correctly', () => {
const badResponse = { message: 'BAD STUFF' };
const response = { id: 123, name: 'Bilbo' };
expectEpic(fetchFooEpic, {
expected: ['-----a|', {
a: { type: 'FETCH_FOO_FULFILLED', payload: response }
}],
action: ['(a|)', {
a: { type: 'FETCH_FOO', payload: { id: 123 } }
}],
response: ['-#-#-a|', {
a: response
}, { xhr: { badResponse } }],
callArgs: [api.fetchFoo, 123]
});
});
...
});
If you check the response in jsbin the actual action in always an empty
array.
Upvotes: 3
Views: 842
Reputation: 143
I had a similar problem where I was trying to test an Angular HttpInterceptor that tries up to three times with a delay between tries. As you mentioned in the comments, the retryWhen resubscribes to the observable after each error. This means that if you have an error observable (e.g. cold('#|')
), you will always get an error in the retryWhen because is resubscribes to the same error observable on each retry.
It seems like a hack, but I created this simple class that subscribes to different observables in the order given.
class MultiObservable extends Observable<any> {
constructor(observables: Observable<any>[]) {
let subscriptionIdx = 0;
super((subscriber: Subscriber<any>) =>
observables[subscriptionIdx++].subscribe(subscriber));
}
}
In my test I use it as follows:
const testObservable = new MultiObservable([
cold('#|', null, { status: 400 }),
cold('a|')
]);
next.handle.and.returnValue(testObservable);
const actual = interceptor.intercept(req, next);
expect(actual).toBeObservable(cold('---a|'));
I'm hoping someone else will have a less hacky solution but this is working for me now.
Upvotes: 2