Я уже некоторое время пишу приложение React / Redux / Saga, но только недавно у меня получилось (благодаря объяснению Kyle на https://davidwalsh.name/es6- генераторы ) как их тестировать. Документация по этой теме была довольно скудной, так что вот что я обнаружил.

Генераторы

Саги реализуются с помощью новой (безумной) функции ES6 под названием «Генераторы». Это функции, которые могут передавать управление основным циклом событий. Если вы когда-нибудь смотрели на redux-saga, вы должны были видеть объявление, подобное следующему:

Первая функция «прослушивает» все MY_ACTION действия, и когда одно из них происходит, она запускает функцию handleMyAction, которая сама является генератором, который отправляет новое действие, созданное функцией создателя действия actionHandled().

Магия этих функций заключается в выражении yield (это становится очевидным, когда о нем забывают и ваша сага не запускается!). Здесь Yield можно рассматривать как кнопку «паузы» в вашей функции, которая позволяет любому асинхронному поведению продолжать свое выполнение.

Более реалистичный пример может содержать несколько шагов, например, если некоторые данные должны быть получены из службы HTTP:

Здесь у нас есть несколько шагов:

  1. Получение URL-адреса из магазина
  2. Вызов API сборщика с URL-адресом в качестве параметра
  3. Отправка действия, указывающего, что вызов API завершился успешно или с ошибкой.

Тестирование саги

Хотя документация Redux-Saga действительно описывает сопоставление эффектов в тестах, в ней нет подробностей о том, как тестировать каждый путь. Для этого требуется немного больше понимания того, как работает Генератор.

Генератор потока

Когда вызывается генератор, он возвращает итератор. На этом этапе ни один код не выполняется, но оцениваются аргументы функции.

Первый вызов next() запускает функцию до первого yield, и выражение справа от ключевого слова yield оценивается и возвращается вызывающему next() под ключом value. Таким образом вы можете проверить, произвела ли сага ожидаемый эффект:

Следующий шаг я нашел самым странным! После вызова первого next() функция «приостановила» поиск значения из состояния, поэтому при возобновлении ей требуется значение для присвоения url. Это достигается передачей аргумента next(); затем генератор переходит к следующему yield:

Затем можно проверить, является ли значение put третьим значением в итераторе:

Следующий вызов next() будет содержать возвращаемое значение генератора (в данном случае ничего) и истинное свойство done.

Исключения

Не сразу понятно, как проверить случай ошибки, но итератор генератора имеет функцию throws, которая приводит к тому, что текущий yield генерирует предоставленный аргумент:

Анализ

Отлично; теперь мы знаем, как тестировать все ветви наших саг… или нет?

Наши тесты очень сильно привязаны к реализации; не просто порядок создаваемых эффектов, а точная последовательность событий, которые должны произойти. Это делает тесты чрезвычайно хрупкими; если бы нужно было добавить одну новую функциональность - скажем, например, дополнительный выбор в качестве первой задачи - тогда каждый отдельный тест сломался бы, и его нужно было бы исправить.

Мы могли бы сделать тесты менее хрупкими, заявив, что «эффект Y наступает после эффекта X», но нам все равно понадобится функция, которая знает, как пройти через все ветви саги.

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