Приоритет этих утверждений при написании тестов компонентов

Знать, что нужно expect() в тестах, — непростая задача. Написав сотни jest, react-testing-library тестов, я считаю, что накопил достаточно опыта, чтобы делиться полезными советами.

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

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

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

Две категории утверждений

Утверждения можно разделить на две категории:

  • Утверждения пользовательского интерфейса
  • Утверждения, не относящиеся к пользовательскому интерфейсу

Какие утверждения вы должны использовать, обычно зависит от тестового примера. Если тест называется updates button text , то совершенно ясно, что вы хотите иметь утверждение пользовательского интерфейса, которое утверждает текст кнопки, например. expect(screen.getByText('Updated button')).toBeInTheDocument()

И наоборот, тест с именем calls API должен иметь утверждение, не относящееся к пользовательскому интерфейсу, например expect(fetchForumThreads).toHaveBeenCalledTimes(1).

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

утверждения пользовательского интерфейса

Утверждения пользовательского интерфейса должны быть в приоритете. Хотя есть часть компонентов, которые не предназначены для визуализации чего-либо визуального (вспомните поставщиков Context API, SEO-оболочек), у большинства компонентов React есть единственная причина существования: производить пользовательский интерфейс.

Таким образом, утверждения пользовательского интерфейса дают вам максимальную уверенность в том, что приложение работает так, как задумано. Если есть что-то, что вы должны вынести из этой записи в блоге, вычеркните это: расставьте приоритеты утверждений пользовательского интерфейса.

Тщательно и эффективно протестированное приложение React начинается с множества тестов, которые определяют, как пользовательский интерфейс изменяется в ответ на взаимодействие с пользователем, течение времени или что-то еще, что может изменить пользовательский интерфейс. По этой причине, когда вы начинаете думать о тестовых примерах компонентов, постарайтесь ответить на этот вопрос: как меняется пользовательский интерфейс?

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

Я могу придумать пару способов, которыми компонент развивает свой пользовательский интерфейс:

  • Первоначально он отображает ноль
  • Счетчик увеличивается на единицу при нажатии +1
  • Счетчик уменьшается на единицу при нажатии -1
  • Счетчик остается равным нулю при нажатии -1

Полезный метод, который действительно помог мне на раннем этапе, заключается в том, чтобы записывать все тестовые примеры перед их реализацией. Или, если вы действительно готовы принять вызов, вы можете попробовать TDD (Test Driven Development), который предполагает написание тестов рука об руку с кодом приложения. В любом случае мы можем использовать it.todo() API Jest:

it.todo('renders zero')
it.todo('increments counter by one on click')
it.todo('decrements counter from one to zero')
it.todo('does not decrement when counter is zero')

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

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

Варианты утверждений пользовательского интерфейса

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

  1. Подтверждение присутствия: expect(screen.getByText('+1')).toBeInTheDocument()
  2. Заявление об отсутствии: expect(screen.queryByText('+1')).not.toBeInTeDocument()
  3. Утверждение атрибута: expect(screen.getByRole('link', { name: 'Threads' })).toHaveAttribute('href', '/forum/threads'))
  4. Снимок: expect(container).toMatchSnapshot()

Обратите внимание, что запрос get RTL используется при подтверждении присутствия, а query — при подтверждении отсутствия. Полезное чтение: Распространенные ошибки с React Testing Library

Моментальные тесты

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

Вы можете захотеть включить утверждения моментальных снимков в свой набор тестов, но имейте в виду, что они сопряжены со своими ловушками! Я подробно останавливаюсь на этом в разделе Что НЕ следует утверждать в тестах компонентов React.

Утверждения без пользовательского интерфейса

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

  • Получить данные
  • Регистрировать события BI (Business Intelligence)
  • Вызов стороннего SDK

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

Какие действия, не связанные с пользовательским интерфейсом, следует протестировать?

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

Я также коснулся этого в разделе Что НЕ следует утверждать в тестах компонентов React, но просто повторю, что мое мнение состоит в том, что утверждение полезно, если это побочный эффект, который важен для пользователя или другим компонентам (подумайте о получении данных, отправке действия Redux, на которое полагаются другие компоненты).

Получение данных

Несколько практических советов по извлечению данных, так как это, пожалуй, самый распространенный вариант использования утверждений, не связанных с пользовательским интерфейсом. Вместо того, чтобы имитировать клиент API (например, axios , fetch ), вы можете взглянуть на MSW (см. >nock» — это сделает ваши тесты менее зависимыми от клиента API.

Однако MSW не рекомендует утверждать, что запрос API был отправлен. Я не согласен. Я вижу преимущество в подтверждении того, что конечная точка API была поражена. Эти утверждения дают уверенность в том, что мы отправляем запрос в нужное время и заданное количество раз.

Я нахожу это особенно полезным при извлечении данных в useEffect . Очень легко неправильно использовать useEffectAPI, в результате чего запрос API будет отправлен больше раз, чем предполагалось. Иллюстрация:

Кстати, mockReturnValue(new Promise(() => undefined)) — это трюк, который можно использовать, чтобы дать обещание никогда не разрешаться. Это помогает избежать act() предупреждений. О том, почему появляются эти предупреждения, я писал в статье Возможно, вам не нужен act() в ваших тестах React.

Интеграционные тесты

В какой-то момент становится актуальным вопрос «должен ли я утверждать логику дочерних компонентов». Это очень правильный вопрос. Это скорее похоже на «должен ли я писать интеграционные тесты?»

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

Однако есть компромиссы. Я рассмотрел случаи, которые, по моему мнению, вы не должны охватывать интеграционными тестами в статье Что НЕ следует утверждать в тестах компонентов React.

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

Если вы сделали это до сих пор — гордитесь, я знаю, что это было непросто! Я надеюсь, что к настоящему времени у вас есть гораздо лучшее представление о том, что вы можете утверждать, и о компромиссах различных типов утверждений. Краткий обзор:

  • Отдавайте приоритет утверждениям пользовательского интерфейса, таким как expect(screen.getByText('Hello world')).toBeInTheDocument() — эти утверждения придают вам наибольшую уверенность.
  • Утверждения, не относящиеся к пользовательскому интерфейсу, такие как expect(logBusinessEvent).toHaveBeenCalledTimes(1)
  • Утверждения, охватывающие логику дочернего компонента

Создавайте компонуемые приложения

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

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

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