Что мы тестируем?

Тестирование снимков Jest в основном используется для тестирования визуализированного вывода компонентов React.

Отрендеренный вывод - это, в конечном счете, элементы, отображаемые в DOM:

Например, вот стандартный компонент React, который отображает заголовок:

Title отображает простой h1 элемент. Моментальный тест гарантирует, что этот компонент выдает h1 при входе children.

Цель состоит в том, чтобы, если мы протестируем визуализированный вывод всех наших компонентов React в приложении, мы сможем эффективно протестировать то, что отображается пользователю.

Вывод с динамическим рендерингом

Конечно, React динамичен.

Отрендеренный вывод может варьироваться в зависимости от ввода (реквизита):

Он также может варьироваться в зависимости от состояния компонента:

При тестировании визуализированного вывода мы хотим убедиться, что охватываем все динамические выводы.

Что не проверяет?

Моментальные тесты не охватывают бизнес-логику компонентов.

Например, рассмотрим следующий компонент:

Тесты моментальных снимков могут гарантировать, что button визуализируется, но они не покрывают это, когда onClick prop будет эффективно очищать localStorage.

Как мы это проверили?

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

# 1 Простые сопоставители ферментов и шуток

Используя Enzyme и Jest, мы можем обернуть наш компонент, чтобы обойти визуализированный вывод и утверждать, что вывод - это то, что мы ожидаем.

# 2 Пользовательские сопоставители ферментов для Jest

jest-enzyme - это пакет, который предоставляет настраиваемые сопоставители, которые упрощают тестирование визуализированного вывода:

Болевые точки

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

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

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

Тестирование с помощью Jest Snapshot Testing

Мы эффективно тестируем наш визуализированный результат гораздо более эффективным способом, чем альтернативные методы, использующие тестирование моментальных снимков.

Давайте наглядно

Представьте, что мы хотим «протестировать» элементы на этой картинке. Мы можем утверждать следующее:

  1. Содержит деревянный фон
  2. Содержит циновку поверх деревянного фона
  3. Содержит листья поверх циновки
  4. Сверху на листьях три яблока

Мы могли бы дать более подробное описание, но вы уловили суть…

А теперь представьте, что «вывод» изображения изменится:

Представьте, что мы изменили «состояние» этой картинки. Нам нужно будет включить эти дополнительные утверждения:

  1. Содержит деревянный фон
  2. Содержит циновку поверх деревянного фона
  3. Содержит листья поверх циновки
  4. На листьях размещено одно яблоко.

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

Основные примеры

Давайте снова протестируем очень простой Title, но на этот раз со снимками:

Используя react-test-renderer, мы берем визуализированный вывод нашего компонента React и переводим его в JSON:

Когда средство запуска тестов Jest впервые замечает toMatchSnapshot теста, оно сгенерирует артефакт моментального снимка (.snap) в каталоге __snapshots__ относительно компонента:

Позже, если визуализированный вывод реализации изменится, средство запуска тестов Jest выделит разницу и предложит обновить сбойный снимок, который можно обновить, нажав u:

Преимущества

  1. Тестирование визуализированного вывода компонента можно объединить в один тест.
  2. Разница между новым снимком и старым снимком легко читается и интерпретируется.
  3. Вам не нужно вручную обновлять какие-либо тесты при изменении визуализированного вывода, средство запуска тестов Jest позаботится об этом за вас.

Установка границ

renderer из react-test-renderer отображает «вниз» к элементам в DOM.

Конечно, во многих случаях в нашем визуализированном выводе присутствует иерархия.

Предположим, у нас есть следующая иерархия:

Наш снимок будет отображаться до h1:

Неглубокий и монтированный рендеринг
Между прочим, в этом разница между shallow и mount рендерингом Enzyme.

«Монтирование» рендеринга идет вниз по иерархии, пока не достигнет конца дерева, элементов DOM.

«Неглубокий» рендеринг просто переходит к непосредственному узлу дерева, рендеринговому выводу, как вы его видите в компоненте React.

Поведение снимков по умолчанию
По умолчанию снимки отражают «монтированный» вывод, обработанный по умолчанию, а не «неглубокий» вывод.

Сохранение неглубокого вывода в снимках
Вы можете предпочесть метод «монтирования» по умолчанию для тестирования, это круто.

Однако у shallow есть то преимущество, что он устанавливает границу ваших снимков, отражающую реализацию.

Другими словами, если мы фиксируем неглубокий вывод в наших снимках, снимки будут напрямую соответствовать тому, что мы видим в компоненте React:

В нашем примере снимок для FancyTitle не идет дальше, чтобы захватить визуализированный вывод Title. Скорее, он фиксирует то, что отображает Title. Тест снимка для Title может зафиксировать то, что он отображает h1.

Это достигается с помощью следующего синтаксического сахара:

jest.mock('./Title', () => 'Title')

Это фактически устанавливает границы наших тестов моментальных снимков так, чтобы они не превышали Title.

Для внешних и именованных модулей мы делаем следующее:

Это дает то преимущество, что вы устанавливаете границы ваших моментальных тестов, тестируя компонент как единое целое. Опять же, снимок отражает визуализированный вывод, как вы видите в реализации компонента.

По моему скромному мнению, это значительно упрощает написание и обзор тестов моментальных снимков.

В сочетании с ферментом

Имеет смысл, что мы хотели бы продолжать использовать Enzyme для обхода визуализированного вывода, чтобы запускать события как «действие»; сделать снимок части визуализированного вывода; тестировать различные сквозные свойства независимо от тестов моментальных снимков; и Т. Д.

Однако экземпляр renderer ожидает элемент React, а не оболочку Enzyme. Как нам это обойти?

Без проблем! Мы можем просто использовать метод getElement в оболочке Enzyme, как только мы будем готовы сгенерировать снимок в тесте:

Более того, мы можем сделать простую renderSnapshot утилиту, которая сделает это за нас:

Обработка динамического рендеринга

Базовые примеры просты, но как насчет того, когда визуализированный вывод является динамическим (на основе входящих свойств или изменений состояния).

Динамический рендеринг с учетом различных свойств
Вот компонент, который отображает другой вывод на основе входящего isLoading свойства:

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

Более того, мне нравится использовать вспомогательную функцию renderComponent, которая по умолчанию генерирует случай «по умолчанию», но позволяет попасть в «особые» случаи, переопределяя предоставленные реквизиты:

Динамическая отрисовка с учетом изменений состояния

Точно так же мы можем отображать разные снимки при изменении состояния. Это выглядит примерно так же, как и в последнем примере, мы просто добавляем «действие» в тест, чтобы попасть в наш особый случай:

Покрытие проходных реквизитов

Есть несколько соображений по поводу покрытия сквозных свойств в тестах моментальных снимков.

Захват произвольных свойств
Обычно компоненты проходят через произвольные свойства с помощью оператора распространения:

Чтобы зафиксировать это в тесте моментального снимка, обязательно включите произвольную опору в renderComponent настройку:

Обработка логических значений
Если вы явно передаете логическое свойство (не используя ...rest), вам потребуется дополнительный тест:

Вы можете использовать Enzyme API, чтобы добиться этого в качестве разового результата в тесте моментального снимка:

Или вы можете сделать дополнительный снимок:

Обработка функций
Есть удобный трюк для захвата сквозных функций в вашем тесте моментальных снимков:

Если бы мы не добавили jest.mockName('onClick'), мы не смогли бы просмотреть снимок, а также определить, является ли свойство onClick сквозной или анонимной функцией.

Теперь ясно, что пропущенная в снимке onClick опора имеет значение входящей onClick опоры.

Возражения против тестирования снимков

Тестирование моментальных снимков кажется отличным, если смотреть на базовые примеры, но как насчет его повседневного использования в кодовой базе?

В отношении удобства тестирования снимков часто упоминается несколько критических замечаний:

  1. Снимки можно обновлять слишком легко
  2. Снимки могут часто дублироваться
  3. Снимки могут быть трудночитаемыми
  4. Снимки могут быть очень длинными

Снимки можно обновлять слишком легко

Средство запуска тестов Jest упрощает обновление снимков. Однако они опасно просты.

Допустим, я обновляю компонент, отображающий ссылку. Я меняю адрес ссылки в одном месте:

Я нажимаю u, чтобы обновить тесты, и я знаю, что у меня все хорошо.

Достаточно просто.

Но допустим, я сделал несколько простых изменений. Я заменяю четыре ссылки в визуализированном выводе на каждую точку на четыре новые ссылки соответственно. Одна из ссылок неверна, но я так привык нажимать u для обновления своих снимков, что она изменилась.

Эффективность средства выполнения тестов только увеличила риск внесения непреднамеренных изменений, нарушающих цель.

Мой ответ
Это возражение исходит из хороших инстинктов. Однако это просто означает, что разработчик должен нести ответственность за тщательный анализ изменений, вносимых в компонент и его соответствующий снимок.

Разработчику следует посмотреть на их различия в реализации и дважды проверить, содержит ли снимок то, что ожидается. Как только это будет подтверждено, рецензент может быть отмечен в обзоре. Рецензент будет нести такую ​​же ответственность.

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

Честно говоря, для того, чтобы это было яснее, необходимо прояснить некоторые другие возражения.

Тестирование снимков может иметь много дублирования

Как уже упоминалось, тесты моментальных снимков не охватывают бизнес-логику компонентов. Этому нужно будет четко научить и акцентировать внимание на команде разработчиков.

Правда, есть некоторая серая зона.

Обычно компонент имеет основную визуализацию вывода, но с небольшими изменениями, основанными на определенных условиях.

Например:

Независимо от того, является ли isLoading истинным или ложным, <h1>{children}</h1> всегда будет отображаться. В этом смысле он является основополагающим.

<Spinner /> с другой стороны, отображается только тогда, когда isLoading истинно.

Я мог бы провести два теста снимков:

  1. Когда isLoading ложно (по умолчанию)
  2. Когда isLoading верно

Первый снимок будет иметь заголовок, но без счетчика.

Второй снимок будет иметь и заголовок, и счетчик.

Если я изменю заголовок на style:

Тогда у меня будет два снимка, которые нужно будет обновить, когда я внесу только одно изменение.

Для более сложных компонентов, что является обычным явлением, одно простое изменение может привести к сбою N снимков.

Это не может быть самым приятным занятием. Это согласуется со следующим возражением.

Снимки бывает трудно читать

Когда между снимками много дублирования, может быть сложно просмотреть файл снимка отдельно и определить, чем отличается снимок «по умолчанию» от снимка «особого случая» (т. Е. Снимок по умолчанию и загрузка).

Решение
Я думаю, есть два возможных решения.

  1. Четко указывайте в названии теста особых случаев, чтобы обозначить разницу.

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

Но есть решение получше.

2. Используйте snapshot-diff

С помощью этой утилиты моментальных снимков вы можете сохранить только разницу между снимками по умолчанию и особыми случаями:

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

Это также имеет то преимущество, что наши файлы снимков становятся меньше и мы можем использовать общие имена тестов снимков.

Снимки могут быть очень длинными

snapshot-diff действительно очень помогает с этой проблемой. Однако есть еще более простое решение.

Решение
Если снимок становится слишком длинным, возможно, пора провести рефакторинг. Если вы посмотрите на тестирование моментальных снимков как на средство поощрения рефакторинга, а не на создание беспорядка, тогда вы, естественно, увидите в этом потенциальном возражении преимущество.

Заключительные слова

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

Это все, ребята. Хлопайте, комментируйте и делитесь!