Unit testing 为使用retryWhen运算符的RxJS编写测试(理解与重试运算符的区别)

Unit testing 为使用retryWhen运算符的RxJS编写测试(理解与重试运算符的区别),unit-testing,rxjs,take,retry-logic,Unit Testing,Rxjs,Take,Retry Logic,我正在尝试为使用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

我正在尝试为使用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));
        }),
      );
    }),
  );
}
// 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
            })
          )
        )
      )
    }),
  );
}
代码应该执行对某个远程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。。。使用simply retry2,测试就成功了

看起来我不太明白如何使用retryWhen实现重试。我怀疑take2正在关闭流,并阻止一切继续。但我不太明白

实际上,我想在retryWhen中编写一些附加逻辑,但首先我需要了解如何正确地使用retryWhen实现重试。或者这实际上是不可能的

额外资源 我的retryWhen+take实现基于以下SO答案:

官方文件:

对于这两个目的,您可以使用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));
        }),
      );
    }),
  );
}
// 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
            })
          )
        )
      )
    }),
  );
}
这里有一个简单的例子

至于理解这一逻辑:

和操作员,根据您参考的官方文档:

如果没有执行错误或完成,则重新订阅可观察的源


这就是为什么在一起时不能通过管道重试和重试。你可以说这些操作员是断链者…

Aha。。。我现在明白了。我误解了引用的SO帖子,我从中获得了在retryWhen中使用take的概念。我得出的结论是,我只需要在retryWhen中关闭错误流,但在那篇文章中,take的使用更多的是一个实现细节——流是通过附加一个错误抛出流来完成的。要完成retryWhen循环,必须抛出一个错误。