Как получить обработчик асинхронной ошибки на наблюдаемой подписке для разрешения и полного выполнения в Jest - PullRequest
0 голосов
/ 02 октября 2018

В настоящее время у меня есть служба, которую я хочу протестировать с помощью инструментов для шутливого и углового тестирования.

async login() {
  const credentials = this.loginForm.value;

  const loadingSpinner = await this.loadingCtrl.create();
  loadingSpinner.present();

  this.authService.login(credentials.email, credentials.password).subscribe(
  data => {
    loadingSpinner.dismiss();
    this.modalCtrl.dismiss();
  },
  async error => {
    loadingSpinner.dismiss();
    const toast = await this.toastCtrl.create({
      message: 'Failed to log in',
      position: 'top',
      cssClass: 'error-toast'
    });
    toast.present();
  }
 );
}

В приведенном выше коде используются ожидания в обработчике ошибок.При попытке использовать fakeAsync с галочкой или сбросом всех микро-задач toastCtrl.create не срабатывает.Когда я переписываю код с помощью асинхронного теста, toast.present не запускается, а создает.Ниже приведен тест и ответ.

    it('should make a call to login fire a toast if there is an error', async(() => {
      authService.login.mockReturnValue(throwError({}));
      component.login().then(() => {
        expect(loadingCtrl.create).toHaveBeenCalled();
        expect(loadingInstance.present).toHaveBeenCalled();
        expect(loadingInstance.dismiss).toHaveBeenCalled();
        expect(toastCtrl.create).toHaveBeenCalledWith({
          message: 'Failed to log in',
          position: 'top',
          cssClass: 'error-toast'
        });
        expect(toastInstance.present).toHaveBeenCalled();
      });

Результат, который я получаю от jest:

 AuthComponent › controller › functions › login › should make a call to login fire a toast if there is an error

Uncaught (in promise): Error: expect(jest.fn()).toHaveBeenCalled()

Expected mock function to have been called, but it was not called.
Error: expect(jest.fn()).toHaveBeenCalled()

Expected mock function to have been called, but it was not called.

  132 |               cssClass: 'error-toast'
  133 |             });
> 134 |             expect(toastInstance.present).toHaveBeenCalled();
      |                                           ^
  135 |           });
  136 |         }));
  137 |       });

У меня простой вопрос: почему в тестах не все решают проблемы.Если вы используете setTimeout с 1000мс и готовый обратный вызов вместо асинхронной оболочки, это работает.

1 Ответ

0 голосов
/ 07 октября 2018

Выпуск

toast.present() по-прежнему находится в очереди в PromiseJobs и не запускается к тому времени, когда expect подтверждает и завершается ошибкой.

Решение

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

it('should make a call to login fire a toast if there is an error', async () => {
  authService.login.mockReturnValue(throwError({}));
  await component.login();   // await login
  expect(loadingCtrl.create).toHaveBeenCalled();
  expect(loadingInstance.present).toHaveBeenCalled();
  expect(loadingInstance.dismiss).toHaveBeenCalled();
  expect(toastCtrl.create).toHaveBeenCalledWith({
    message: 'Failed to log in',
    position: 'top',
    cssClass: 'error-toast'
  });
  await Promise.resolve();   // allow pending callbacks in PromiseJobs to run
  expect(toastInstance.present).toHaveBeenCalled();   // SUCCESS
});

Подробности

Обратные вызовы, переданные в subscribe, не являются частью component.login, поэтомуобратные вызовы могут выполняться, а могут и не выполняться, когда Promise, возвращенный из component.login, разрешается и тест продолжается.

В этом случае вызывается обратный вызов error и выполняется вызов createно остальная часть обратного вызова еще не запущена к тому моменту, когда expect(toastInstance.present).toHaveBeenCalled(); запускается и завершается неудачей.

В идеале непосредственно тестирует await любой тестируемый асинхронный код, но так как это невозможно с асинхронным обратным вызовом, в качестве альтернативыозначает await разрешенное значение Promise, которое, по существу, ставит в очередь оставшуюся часть теста в конце PromiseJobs за любыми ожидающими обратными вызовами, давая другим функциям обратного вызова возможность выполнить перед продолжением теста.

Примечание: Вы могли заметить, чтоt Jest полностью выходил с необработанным исключением вместо того, чтобы просто сообщить, что тест не пройден.То, как тест написан в вопросе, Promise, возвращаемое из component.login().then(), не возвращается и не await -ed, поэтому тест завершается, и неудачный expect в обратном вызове then в конечном итоге происходит вне теста, вызываяJest для выхода.Самый простой способ решить эту проблему (реализовано в приведенном выше решении) - удалить обратный вызов then и просто await Promise, возвращенный из component.login перед началом expect утверждений.

...