Избегайте этих тестовых утверждений в тестах Jest, React Testing Library.

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

Один из самых страшных вопросов, которые у меня возникали, был как мне узнать, что я должен expect() в тестах компонентов React? На него невозможно ответить в коротком посте в блоге. Поэтому, чтобы сделать его более удобоваримым, в этом сообщении блога я сосредоточусь только на том, что вы НЕ должны утверждать в тестах компонентов React.

Примечание: фрагменты кода основаны на Jest и React Testing Library (RTL)

Детали реализации

«Детали реализации» — это расплывчатый термин, и разные люди могут относиться к разным вещам, но очень важно понимать компромиссы при тестировании деталей реализации.

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

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

Однако что, если имя вашего класса определяет, является ли компонент видимым или скрытым? В этом случае я бы предпочел стиснуть зубы и утвердить имя класса, потому что это дает мне уверенность в том, что компонент виден/спрятан правильно.

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

Некоторые конкретные нельзя, на которые следует обратить внимание:

  • Использование структуры DOM. Избегайте использования within() , .children , .parentElement и других атрибутов/помощников, которые зависят от структуры DOM.
  • Имена классов, например expect(button.className).toContain('with-loader') . Вы не можете осмысленно протестировать фактический пользовательский интерфейс с помощью тестов компонентов. Однако лично у меня есть одно исключение — я бы протестировал имена классов, жизненно важные для функциональности компонента, например, скрытый/видимый.
  • Внутреннее состояние компонента. На самом деле, RTL не предоставляет вам API для достижения этого, но я просто хочу подчеркнуть, что вы не должны стремиться к этому.

eslint-plugin-testing-library — ваш друг здесь (хотя, как участник, я несколько предвзят 😶). Это может помочь вам найти так много антипаттернов.

Не утверждайте сторонний код

Всегда думайте о том, какой код утверждается в ваших тестах. Это ваш код? Это код сторонней библиотеки?

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

Кроме того, в некоторых случаях вы должны имитировать модули, такие как клиент API (fetch, axios), потому что вы не хотите вызывать реальные конечные точки в своих тестах.

Забавный факт: при каждом тестировании компонента React вы также тестируете сторонний код, потому что вы запускаете жизненный цикл React, следовательно, вы тестируете сам React, а также трансформер JSX Babel.

Но вы не хотите в конечном итоге утверждать вещи, которые находятся вне вашего контроля. Например, вы можете утверждать window.addEventListener в своих тестах: expect(addEventListener).toHaveBeenCalledTimes(5). Однако это, по сути, означает, что вы утверждаете, что React работает правильно. В этом мало смысла. Он уже хорошо протестирован.

Практический пример: вместо того, чтобы нажимать <a /> и утверждать, что URL-адрес изменился, вы можете сделать следующее: expect(screen.getByRole('link')).toHaveAttribute('href', 'google.com')

Я также видел, как люди пишут интересные утверждения:expect(render(<div />)).not.toBeNull()

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

Слишком много снэпшот-тестов

Jest предоставляет способ написания моментальных тестов, что является причудливым названием для теста с expect(...).toMatchSnapshot() утверждением. Я думаю, что Эффективное тестирование моментальных снимков Кента С. Доддса стоит прочитать, если вы хотите еще больше углубиться в то, что можно и чего нельзя делать.

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

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

  • общий* тест моментальных снимков, служащий защитой от непредвиденных изменений (например, изменение сторонней библиотеки пользовательского интерфейса).
  • Когда тест без снимка слишком хрупок. Например, если у вас много утверждений пользовательского интерфейса или если некоторые из них зависят от реализации (например, опора на структуру DOM с использованием within() из RTL или утверждения имен классов), то кажется более целесообразным провести тест с моментальным снимком, потому что тест проще и может обновляться более интерактивным способом.

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

Кстати, одна хитрость, о которой я изначально не знал: можно делать «частичные снимки». Вместо expect(container).toMatchSnapshot() можно сделать expect(screen.getByTestId('dropdown-button')).toMatchSnapshot()

Побочные эффекты, которые НЕ важны для пользователя или других компонентов

Вы можете задаться вопросом — какие действия, не связанные с пользовательским интерфейсом, следует протестировать? Если мой компонент вызывает функцию transformDateToText(), должен ли быть тест с утверждением expect(transformDateToText).toHaveBeenCalledTimes(1)?

В этом конкретном случае, если предположить, что функция transformDateToText используется для отображения приятного свидания, вероятно, нет. Это можно проверить с помощью утверждений пользовательского интерфейса (например, expect(screen.getByText('5 days ago')).toBeInTheDocument()), что дает больше уверенности.

К side effect that is important to the user подумайте о получении данных. Обычно вы можете протестировать выборку данных с помощью утверждений пользовательского интерфейса, но для пользователя важно, чтобы вы не спамили запросы. Если вы без необходимости отправите 10 запросов GET, мобильные данные пользователей исчезнут.

By a side effect that is important to other components — подумайте о чем-то вроде Redux dispatch(). Для других компонентов может быть важно, чтобы отправлялись правильные данные, поэтому это хороший кандидат на утверждение.

Утверждение логики дочерних компонентов

Один вопрос, который у вас может возникнуть: «Должен ли я утверждать логику дочерних компонентов»? Это очень хороший вопрос. Это примерно переводится как «должен ли я писать интеграционные тесты?» И мой грубый ответ — в большинстве случаев да.

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

  • Если дочерний компонент предназначен для многократного использования, например <ForumBreadcrumbs /> , то я бы не стал обширно* покрывать его интеграционными тестами, потому что тестирование одной и той же функциональности дочернего компонента десятками наборов тестов сопряжено со значительными затратами на обслуживание и производительность. Уверенность, которую вы получаете, тестируя один и тот же компонент 10 раз, а не, скажем, 2 раза, не имеет значения.
  • Утверждения, не связанные с пользовательским интерфейсом, которые охватывают логику, которая реализована строго в дочернем компоненте. Под строго я подразумеваю, чтокомпонент не имеет никакого контроля над тем,отклоняется ли утверждение или нет.Например, я бы не стал утверждать, что дочерний компонент вызывает API. Наш компонент не передает свойство с вызовом API и не имеет никакого другого способа контролировать, выполняет ли дочерний компонент вызов API. вызов API. Если у него есть какой-то контроль, я бы проверил его.

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

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

Краткое содержание

Вот краткий обзор того, каких утверждений я рекомендую избегать в тестах компонентов React:

  • Утверждения, основанные на структуре DOM
  • Имена классов, ЕСЛИ это не является жизненно важным для функциональности компонента (например, имя класса скрывает узел DOM)
  • Внутреннее состояние компонента
  • сторонний код
  • Слишком много снэпшот-тестов
  • Побочные эффекты, НЕ важные для пользователя или других компонентов, такие как transformDateToText()
  • Многоразовые дочерние компоненты
  • Логика, не относящаяся к пользовательскому интерфейсу, которая реализована строго в дочерних компонентах.

Создавайте компонуемые интерфейс и серверную часть

Не создавайте веб-монолиты. Используйте Bit для создания и компоновки несвязанных программных компонентов — в ваших любимых фреймворках, таких как React или Node. Создавайте масштабируемые и модульные приложения с мощными и приятными возможностями разработки.

Перенесите свою команду в Bit Cloud, чтобы совместно размещать и совместно работать над компонентами, а также значительно ускорить, масштабировать и стандартизировать разработку в команде. Начните с компонуемых интерфейсов, таких как Design System или Micro Frontends, или исследуйте компонуемый сервер. Попробуйте →

Узнать больше









Первоначально опубликовано на https://zaicevas.com/