Как преодолеть разрыв между временем вызова функций и наблюдаемыми объектами
Асинхронный код - это сложно! Сложно писать и тестировать.
RxJs изменил наше представление об асинхронности. Вместо использования Promises мы сейчас имеем дело с потоками в форме Observables или Subjects. RxJs предоставляет нам множество готовых операторов для создания, объединения или преобразования потоков.
Но мы получаем не только отличные инструменты для исполняемого кода, но и отличные инструменты для тестирования потоков.
Диаграммы из мрамора - отличный способ моделирования потоков. Они используются во многих руководствах, а теперь также могут использоваться в тестах для утверждения потоков.
Если вы никогда не слышали о тестировании мрамора, я рекомендую вам ознакомиться со следующей статьей.
Мраморное тестирование - это здорово! Многие люди используют его для тестирования своих наблюдаемых. Но как насчет предметов?
Пицца
В настоящее время большинство фреймворков используют концепцию компонентов. При работе с компонентами часто требуется способ связи между ними. Обычно у вас есть несколько способов реализации связи между компонентами.
Angular, например, предлагает обмен данными между компонентами через @Input
и @Output
, обмен данными через службу или, для более сложного варианта использования, использование библиотеки управления состоянием, такой как ngrx.
Для простых случаев использования услуга с темой часто оказывается хорошим решением. Представьте себе приложение, в котором вы можете заказать пиццу.
Пиццу можно заказать, нажав кнопку в элементе списка. Щелчок по кнопке затем использует OrderService
для информирования корзины покупок о добавлении новой пиццы. Давайте посмотрим на код OrderService
.
В нашем OrderService
есть общедоступный orderPizza
метод, который next
добавит новую пиццу в наш pizza$
.
☝ ️ Никогда не открывайте
Subject
напрямую. Всегда лучше открывать только Observable. Для этого вы всегда можете использоватьasObservable
метод Subject. Чтобы упростить задачу и сосредоточиться на задаче тестирования, мы пока оставим это в стороне.
ОК, круто. Мы реализовали безупречный сервис. Давай проверим. 😃
Есть несколько способов протестировать такие сервисы. Мы можем использовать классический шаблон «подписаться и подтвердить» или «мраморное тестирование». Если вы хотите узнать больше об их различиях и о том, что вам следует использовать, я рекомендую вам чтобы проверить эту статью:
Мы заинтересованы в испытании мрамора в этой услуге.
Мрамор испытайте предмет 🧪
Для начала мы сначала создаем экземпляры нашей sut
(тестируемой системы) и TestScheduler
.
При создании экземпляра TestScheduler
мы передаем assertDeppEqual
функцию, которая сообщает TestScheduler
, как сравнивать значения. С этого момента мы можем начать использовать TestScheduler
для написания нашего мраморного теста.
Мы хотим проверить, что наша pizza$
Subject отправляет правильную пиццу.
Мы используем деструктуризацию, чтобы получить доступ к вспомогательным методам TestScheduler’s
expectObservable
и cold
. Затем мы используем функцию expectObservable
в сочетании с синтаксисом Marble, чтобы гарантировать, что наш ожидаемый Observable является Observable, который передает одно значение, которым является «Tonno».
Легко, не правда ли? Давай запустим.
Error: expect(received).toEqual(expected) // deep equality - Expected - 11 + Received + 1 - Array [ - Object { - "frame": 0, - "notification": Notification { - "error": undefined, - "hasValue": true, - "kind": "N", - "value": "Tonno", - }, - }, - ] + Array []
О нет. 😲 Наш тест не проходит. Что случилось?
Если вам понравился этот пост в блоге и вы хотите быть в курсе новых интересных вещей, которые происходят в современной фронтенд-разработке - подписывайтесь на меня в Twitter.
Давайте это проанализируем. В сообщении об ошибке говорится, что мы ожидали, что значение «Tonno» будет выдано на frame
0, но мы не получили никакого значения. Это почему?
Давайте подробнее рассмотрим вызовы функций и порядок их выполнения.
- В нашем тесте мы вызываем функцию
orderPizza
sut.orderPizza(pizzaTonno);
2. pizzaTonno
присоединяется к нашему Subject
.
this.pizza$.next(pizza);
3. Наш тест подписывается на открытые Subject
expectObservable(sut.pizza$).toBe('a', {a: pizzaTonno});
Из этого порядка мы видим, что это проблема времени. Значение «присоединяется» к Subject
перед подпиской. После того, как мы подписались, никакая ценность не транслируется. Поэтому получаем пустой массив.
Нам нужно преодолеть разрыв между временем вызовов функций и Observables.
Преодоление разрыва 🌉
Чтобы настроить этот тест, нам нужно изменить время вызова функции orderPizza
. Вместо того, чтобы вызывать его немедленно, нам нужно вызвать его в том же кадре, что и подписка.
Для этого мы можем использовать вспомогательный метод TestScheduler’s
cold
. Как видно из названия, функция cold
создает холодный Observable из мраморной диаграммы. Мы можем subscribe
к такому Observable и вызвать orderPizza
функцию.
cold('-a').subscribe(() => sut.orderPizza(pizzaTonno))
Тогда наш полный тест будет выглядеть так:
Мы преодолели разрыв, и теперь у нашего теста зеленый цвет. Мы успешно проверили простую услугу на Тема.
Конечно, в этом простом примере также можно было бы использовать шаблон «подписаться и подтвердить». Но тогда нам придется иметь дело с подпиской и обратным вызовом done
.
Вам решать, какой подход более краткий и понятный.
Дополнительные ресурсы о тестировании мрамора
Если вы хотите узнать больше о тестировании мрамора и TestScheduler
, вам может быть интересна одна из моих статей о RxJS и тестировании.
🧞 🙏 Если вам понравился этот пост, поделитесь им и хлопните в ладоши👏🏻, нажав несколько раз на кнопку хлопка слева.
Хлопки помогают другим людям открывать для себя контент и мотивируют меня писать больше. 😉