为使用 retryWhen 运算符的 RxJS 编写测试(了解与重试运算符的区别)
Writing tests for RxJS that uses retryWhen operator (understanding difference from retry operator)
我正在尝试为以下使用 retryWhen
运算符的函数编写测试:
// some API I'm using and mocking out in test
import { geoApi } from "api/observable";
export default function retryEpic(actions$) {
return actions$.pipe(
filter(action => action === 'A'),
switchMap(action => {
return of(action).pipe(
mergeMap(() => geoApi.ipLocation$()),
map(data => ({ data })),
retryWhen(errors => {
return errors.pipe(take(2));
}),
);
}),
);
}
代码应该执行对某个远程 API geoApi.ipLocation$()
的请求。如果出现错误,它会在放弃之前重试 2 次。
我编写了以下使用 Jest 和 RxJS TestScheduler 的测试代码:
function basicTestScheduler() {
return new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
}
const mockApi = jest.fn();
jest.mock('api/observable', () => {
return {
geoApi: {
ipLocation$: (...args) => mockApi(...args),
},
};
});
describe('retryEpic()', () => {
it('retries fetching 2 times before succeeding', () => {
basicTestScheduler().run(({ hot, cold, expectObservable, expectSubscriptions }) => {
const actions$ = hot('-A');
// The first two requests fail, third one succeeds
const stream1 = cold('-#', {}, new Error('Network fail'));
const stream2 = cold('-#', {}, new Error('Network fail'));
const stream3 = cold('-r', { r: 123 });
mockApi.mockImplementationOnce(() => stream1);
mockApi.mockImplementationOnce(() => stream2);
mockApi.mockImplementationOnce(() => stream3);
expectObservable(retryEpic(actions$)).toBe('----S', {
S: { data: 123 },
});
expectSubscriptions(stream1.subscriptions).toBe('-^!');
expectSubscriptions(stream2.subscriptions).toBe('--^!');
expectSubscriptions(stream3.subscriptions).toBe('---^');
});
});
});
此测试失败。
但是,当我将 retryWhen(...)
替换为简单的 retry(2)
时,测试成功。
看来我不太明白如何用 retryWhen
实现 retry
。我怀疑这个 take(2)
正在关闭流并阻止一切继续进行。但是我不是很明白。
我其实想在retryWhen()
里面写一些额外的逻辑,但首先我需要了解如何用retryWhen()
正确地实现retry()
。或者这实际上是不可能的?
其他资源
我对 retryWhen
+ take
的实现是基于这个 SO 答案:
官方文档:
你可以使用 retryWhen
来达到这两个目的,一个是你的逻辑,第二个是你想给它的重试次数(不需要使用 retry
运算符):
// some API I'm using and mocking out in test
import { geoApi } from "api/observable";
export default function retryEpic(actions$) {
return actions$.pipe(
filter(action => action === 'A'),
switchMap(action => {
return of(action).pipe(
mergeMap(() => geoApi.ipLocation$()),
map(data => ({ data })),
retryWhen(errors =>
errors.pipe(
mergeMap((error, i) => {
if (i === 2) {
throw Error();
}
// return your condition code
})
)
)
)
}),
);
}
这是一个简单的 DEMO。
至于理解这个逻辑:
retryWhen
and retry
运算符,根据您引用的官方文档:
resubscribing to the source Observable (if no error or complete executes)
这就是为什么不能将 retry
和 retryWhen
连接在一起的原因。可以说这些运营商是链条破坏者...
我正在尝试为以下使用 retryWhen
运算符的函数编写测试:
// some API I'm using and mocking out in test
import { geoApi } from "api/observable";
export default function retryEpic(actions$) {
return actions$.pipe(
filter(action => action === 'A'),
switchMap(action => {
return of(action).pipe(
mergeMap(() => geoApi.ipLocation$()),
map(data => ({ data })),
retryWhen(errors => {
return errors.pipe(take(2));
}),
);
}),
);
}
代码应该执行对某个远程 API geoApi.ipLocation$()
的请求。如果出现错误,它会在放弃之前重试 2 次。
我编写了以下使用 Jest 和 RxJS TestScheduler 的测试代码:
function basicTestScheduler() {
return new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
}
const mockApi = jest.fn();
jest.mock('api/observable', () => {
return {
geoApi: {
ipLocation$: (...args) => mockApi(...args),
},
};
});
describe('retryEpic()', () => {
it('retries fetching 2 times before succeeding', () => {
basicTestScheduler().run(({ hot, cold, expectObservable, expectSubscriptions }) => {
const actions$ = hot('-A');
// The first two requests fail, third one succeeds
const stream1 = cold('-#', {}, new Error('Network fail'));
const stream2 = cold('-#', {}, new Error('Network fail'));
const stream3 = cold('-r', { r: 123 });
mockApi.mockImplementationOnce(() => stream1);
mockApi.mockImplementationOnce(() => stream2);
mockApi.mockImplementationOnce(() => stream3);
expectObservable(retryEpic(actions$)).toBe('----S', {
S: { data: 123 },
});
expectSubscriptions(stream1.subscriptions).toBe('-^!');
expectSubscriptions(stream2.subscriptions).toBe('--^!');
expectSubscriptions(stream3.subscriptions).toBe('---^');
});
});
});
此测试失败。
但是,当我将 retryWhen(...)
替换为简单的 retry(2)
时,测试成功。
看来我不太明白如何用 retryWhen
实现 retry
。我怀疑这个 take(2)
正在关闭流并阻止一切继续进行。但是我不是很明白。
我其实想在retryWhen()
里面写一些额外的逻辑,但首先我需要了解如何用retryWhen()
正确地实现retry()
。或者这实际上是不可能的?
其他资源
我对 retryWhen
+ take
的实现是基于这个 SO 答案:
官方文档:
你可以使用 retryWhen
来达到这两个目的,一个是你的逻辑,第二个是你想给它的重试次数(不需要使用 retry
运算符):
// some API I'm using and mocking out in test
import { geoApi } from "api/observable";
export default function retryEpic(actions$) {
return actions$.pipe(
filter(action => action === 'A'),
switchMap(action => {
return of(action).pipe(
mergeMap(() => geoApi.ipLocation$()),
map(data => ({ data })),
retryWhen(errors =>
errors.pipe(
mergeMap((error, i) => {
if (i === 2) {
throw Error();
}
// return your condition code
})
)
)
)
}),
);
}
这是一个简单的 DEMO。
至于理解这个逻辑:
retryWhen
and retry
运算符,根据您引用的官方文档:
resubscribing to the source Observable (if no error or complete executes)
这就是为什么不能将 retry
和 retryWhen
连接在一起的原因。可以说这些运营商是链条破坏者...