Действие при ошибке тестирования - мрамор - ngrx Effects - PullRequest
1 голос

У меня проблема с тестированием неудачного действия над моими эффектами.

Чтобы дать здесь немного контекста, эффект loadProducts выполняется, когда вызывается действие Load . Внутри эффекта выполняется HTTP-запрос, в случае успешного выполнения этого запроса вызывается действие LoadSuccess , в противном случае вызывается LoadFail . Ниже приведен код

  @Effect()
  loadProducts$ = this.actions$.pipe(
    ofType(productActions.ProductActionTypes.Load),
    mergeMap((action: productActions.Load) =>
      this.productService.getProducts().pipe(
        map((products: Product[]) => (new productActions.LoadSuccess(products))),
        catchError(error => of(new productActions.LoadFail(error)))
      ))
  );

Чтобы проверить этот эффект, я использовал jest-marbles, которые почти такие же, как jasmine-marbles, в любом случае, я создал действие Load как горячее наблюдаемое, мой HTTP-ответ как холодный и ожидаемый результат по умолчанию.

it('should return a LoadFail action, with an error, on failure', () => {
  const action = new Load();
  const errorMessage = 'Load products fail';
  const outcome = new LoadFail(errorMessage);

  actions$ = hot('-a', { a: action});

  const response = cold('-#|', {}, errorMessage);
  productServiceMock.getProducts = jest.fn(() => response);

  const expected = cold('--(b|)', { b: outcome });

  expect(effects.loadProducts$).toBeObservable(expected);
});

Когда я запускаю тест, выдает ошибку, говоря, что мои loadProducts наблюдаемые и ожидаемый результат не совпадает.

  ✕ should return a LoadFail action, with an error, on failure (552ms)

Product effects › loadProducts › should return a LoadFail action, with an error, on failure

expect(received).toBeNotifications(expected)

Expected notifications to be:
  [{"frame": 20, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"payload": "Load products fail", "type": "[Product] Load Fail"}}}, {"frame": 20, "notification": {"error": undefined, "hasValue": false, "kind": "C", "value": undefined}}]
But got:
  [{"frame": 20, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"payload": "Load products fail", "type": "[Product] Load Fail"}}}]

Difference:

- Expected
+ Received

  Array [
    Object {
      "frame": 20,
      "notification": Notification {
        "error": undefined,
        "hasValue": true,
        "kind": "N",
        "value": LoadFail {
          "payload": "Load products fail",
          "type": "[Product] Load Fail",
        },
      },
    },
-   Object {
-     "frame": 20,
-     "notification": Notification {
-       "error": undefined,
-       "hasValue": false,
-       "kind": "C",
-       "value": undefined,
-     },
-   },
  ]

Я знаю, в чем ошибка, но не знаю, как чтобы решить это. Меня знают в мире испытаний шариков

Ответы [ 2 ]

2 голосов
/ 07 мая 2020

Я хотел бы объяснить, почему это вообще не сработало.

Как вы знаете, когда вы тестируете наблюдаемые с помощью мраморных диаграмм, вы не используете реальные время , но виртуальное время . Виртуальное время можно измерить в frames. Значение кадра может варьироваться (например, 10, 1), но независимо от значения это то, что помогает проиллюстрировать ситуацию, с которой вы имеете дело.

Например, с hot(--a---b-c) , вы описываете наблюдаемое, которое будет выдавать следующие значения: a в 2u, b в 6u и c в 8u (u - единицы времени).

Внутренне, Rx Js создает очередь действий , и задача каждого действия состоит в том, чтобы выдать значение, которое ему было присвоено. {n}u описывает, когда действие выполнит свою задачу.

Для hot(--a---b-c) очередь действий будет выглядеть примерно так:

queue = [
  { frame: '2u', value: 'a' }/* aAction */, 
  { frame: '6u', value: 'b' }/* bAction */, 
  { frame: '8u', value: 'c' }/* cAction */
]

* При вызове 1033 * и cold будут созданы экземпляры наблюдаемых hot и cold соответственно. Их базовый класс расширяет класс Observable.

Теперь очень интересно посмотреть, что происходит, когда вы имеете дело с внутренними наблюдаемыми, как показано в вашем примере:

actions$ = hot('-a', { a: action}); // 'a' - emitted at frame 1

const response = cold('-#|', {}, errorMessage); // Error emitted at 1u after it has been subscribed
productServiceMock.getProducts = jest.fn(() => response);

const expected = cold('--(b|)', { b: outcome }); // `b` and `complete` notification, both at frame 2

response observable подписан из-за a, что означает, что уведомление об ошибке будет отправлено по адресу frame of a + original frame. То есть frame 1 (прибытие a) + frame1 (при возникновении ошибки) = frame 2.

Итак, почему hot('-a') не сработало?

Это из-за того, как mergeMap обрабатывает вещи. При использовании mergeMap и его братьев и сестер, если источник завершается, но оператор имеет внутренние наблюдаемые, которые все еще активны ( еще не завершено ), полное уведомление источника не будет передано. Это произойдет только тогда, когда все внутренние наблюдаемые также будут завершены.

С другой стороны, если все внутренние наблюдаемые объекты завершены, но источник не завершил работу, полное уведомление не будет передано следующему подписчик в сети. Вот почему он изначально не работал .

Теперь давайте посмотрим, почему это работает так:

actions$ = hot('-a|', { a: action});

const response = cold('-#|)', {}, errorMessage);
productServiceMock.getProducts = jest.fn(() => response);

const expected = cold('--(b|)', { b: outcome });

очередь действий теперь будет выглядеть так:

queue = [
  { frame: '1u', value: 'a' },
  { frame: '2u', completeNotif: true },
]

При получении a будет подписана response, и поскольку это наблюдаемое, созданное с помощью cold(), его уведомления будут должны быть назначены действиям и помещены в очередь соответственно.

После того, как response был подписан, очередь будет выглядеть следующим образом:

queue = [
  // `{ frame: '1u', value: 'a' },` is missing because when an action's task is done
  // the action itself is removed from the queue

  { frame: '2u', completeNotif: true }, // Still here because the first action didn't finish
  { frame: '2u', errorNotif: true, name: 'Load products fail' }, // ' from '-#|'
  { frame: '3u', completeNotif: true },// `|` from '-#|'
]

Обратите внимание, что если 2 действия очереди должен передаваться в том же кадре, приоритет будет иметь самый старый.

Из вышеизложенного мы можем сказать, что источник будет выдавать полное уведомление до внутренний наблюдаемый объект выдаст ошибку, это означает, что когда внутренний наблюдаемый будет выдавать значение, полученное в результате обнаружения ошибки (outcome), mergeMap будет передавать полное уведомление.

Наконец, (b|) необходим в cold('--(b|)', { b: outcome });, потому что наблюдаемое catchError su bscribes to, of(new productActions.LoadFail(error))), будет сгенерирован и завершен в том же кадре. Текущий кадр содержит значение кадра текущего выбранного действия. В данном случае это 2, из { frame: '2u', errorNotif: true, name: 'Load products fail' }.

0 голосов

Я нашел способ решить мою проблему, не уверен, что это лучший способ сделать это, но в основном я добавил канал для завершения горячего наблюдаемого. Пожалуйста, дайте мне знать, если есть другое решение.

 it('should return a LoadFail action, with an error, on failure', () => {
  const action = new Load();
  const errorMessage = 'Load products fail';
  const outcome = new LoadFail(errorMessage);

  actions$ = hot('-a|', { a: action});

  const response = cold('-#|)', {}, errorMessage);
  productServiceMock.getProducts = jest.fn(() => response);

  const expected = cold('--(b|)', { b: outcome });

  expect(effects.loadProducts$).toBeObservable(expected);
});
...