Как преодолеть разрыв между временем вызова функций и наблюдаемыми объектами

Асинхронный код - это сложно! Сложно писать и тестировать.

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, но мы не получили никакого значения. Это почему?

Давайте подробнее рассмотрим вызовы функций и порядок их выполнения.

  1. В нашем тесте мы вызываем функцию 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 и тестировании.









🧞‍ 🙏 Если вам понравился этот пост, поделитесь им и хлопните в ладоши👏🏻, нажав несколько раз на кнопку хлопка слева.

Хлопки помогают другим людям открывать для себя контент и мотивируют меня писать больше. 😉