Написание тестов для Rx JS с использованием оператора retryWhen (понимание отличия от оператора retry) - PullRequest
0 голосов
/ 16 января 2020

Я пытаюсь написать тесты для следующей функции, которая использует оператор 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 и Rx JS 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), тогда тест завершается успешно.

Похоже, я не совсем понимаю, как реализовать retry с retryWhen. Я подозреваю, что take(2) закрывает поток и препятствует тому, чтобы все продолжалось. Но я не совсем понимаю.

Я на самом деле хочу написать некоторые дополнительные логики c внутри retryWhen(), но сначала мне нужно понять, как правильно реализовать retry() с retryWhen(). Или, возможно, это на самом деле невозможно?

Дополнительные ресурсы

Моя реализация retryWhen + take была основана на этом ответе SO:

Официальные документы:

1 Ответ

1 голос
/ 20 января 2020

Вы можете использовать retryWhen для этих двух целей, одна для того, чтобы в ней была ваша логика c, а вторая - это числа повторов, которые вы хотели бы дать (не нужно использовать оператор 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
            })
          )
        )
      )
    }),
  );
}

Вот простое ДЕМО этого.

Что касается понимания этой логики c:

retryWhen и retry операторов, в соответствии с официальными документами, на которые вы ссылались:

повторная подписка на источник Observable (если нет ошибок или завершено выполнение)

Вот почему вы не можете соединить retry и retryWhen вместе. Можно сказать, что эти операторы являются прерывателями цепи ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...