React Junior
208 subscribers
37 photos
462 links
Изучение React с нуля
Download Telegram
ByDisplayValue

Еще один запрос для интерактивных элементов, у которых может быть value (input, textarea, select). Соответственно поиск происходит по текущему значению value - это текст, который отображается в инпуте. Настроек у запроса немного:


screen.getByDisplayValue(
value: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}
)


#тестирование #testinglibrary #документация #примерыкода
👍1
ByText

Есть возможность искать элементы по текстовому контенту. Это сработает для всех элементов, у которых есть textContent, а также для инпутов с типом submit или button.

Настройки у запроса стандартные:


getByText(
text: TextMatch,
options?: {
selector?: string = '*',
exact?: boolean = true,
ignore?: string|boolean = 'script, style',
normalizer?: NormalizerFn,
})

Новая для нас настройка - ignore, она указывает, какие селекторы игнорировать при поиске.

#тестирование #testinglibrary #документация #примерыкода
👍1
ByAltText, ByTitle

Еще два запроса для поиска по значению атрибутов:

- alt - в основном для изображений
- title


screen.getByAltText(
text: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
})

screen.getByTitle(
title: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
})


#тестирование #testinglibrary #документация #примерыкода
👍1
ByTestId

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


screen.getByTestId(
text: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
})


#тестирование #testinglibrary #документация #примерыкода
👍1
Testing Library. Вызов событий. fireEvent

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

Testing Library предоставляет несколько способов эмулировать такие события. Самый простой - метод fireEvent из пакета @testing-library/dom.

Документация нам говорит, что в большинстве случаев мы будем использовать другой пакет - @testing-library/user-event, но пока посмотрим на этот.

Метод fireEvent принимает первым аргументом элемент, на котором необходимо вызвать событие. Вторым - объект события.


fireEvent(input, new MouseEvent('click'))


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


fireEvent.change(input, { target: { value: 'hello' } })
fireEvent.keyDown(domNode, {key: 'Enter', code: 'Enter', charCode: 13})


Дока тут: https://testing-library.com/docs/dom-testing-library/api-events

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

Пример: https://codesandbox.io/p/devbox/testing-library-global-jsdom-screen-react-junior-forked-7t9cmt

#тестирование #testinglibrary #документация #примерыкода
👍2
Testing Library. Асинхронщина. waitFor

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

waitFor

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

Мы можем даже использовать expect для проверки условий, так как при несоответствии он как раз выбрасывает ошибку:


await waitFor(() => expect(mockAPI).toHaveBeenCalledTimes(1))


Вместо коллбэка-функции можно передать промис, он не будет вызываться повторно.

Вторым аргументом можно передать объект с настройками:

- container: HTMLElement - по умолчанию document
Если мы ждем появления элемента внутри конкретного контейра, то можно передать его
- timeout - время ожидания
- interval - как часто вызывать коллбэк
- onTimeout: (error: Error) => Error - по умолчанию добавляет к ошибке текущее состояние элемента container
- mutationObsereverOptions - для настройки вызова коллбэка при изменениях контейнера

#тестирование #testinglibrary #документация #примерыкода
👍2👏1
Testing Library. Асинхронщина. findBy

Мы помним, что у нас есть три вида запросов - getBy, queryBy и findBy. Так вот findBy - это комбинация getBy и уже рассмотренного выше метода waitFor. Таким образом, запрос findBy по умолчанию ожидает, когда искомый элемент появится на странице.

Можно использовать в комбинации с await:


await screen.findByText('Clicked once')


#тестирование #testinglibrary #документация #примерыкода
👍1
Testing Library. Асинхронщина. waitForElementToBeRemoved

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

Первым параметром она принимает элемент или массив элементов. Кроме того, можно передать функцию-коллбэк, которая вернет элемент или массив элементов. Если элемент null или массив пустой, будет ошибка.


const el = document.querySelector('div.getOuttaHere')
await waitForElementToBeRemoved(el)


Вторым параметром можно передать объект с настройками, такой же как у функции waitFor.

#тестирование #testinglibrary #документация #примерыкода
👍1
Testing Library. Размышления о fireEvent

Хорошая статья в документации про особенности эмуляции событий: https://testing-library.com/docs/guide-events

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

Например, если мы вызываем метод fireEvent.click(element), он задиспатчит событие клика и сработает обработчик клика, если он есть. В большинстве случаев нам этого более чем достаточно. Однако когда настоящий юзер кликает на настоящий элемент, мы получаем гораздо больше событий: mouseOver, mouseMove, mouseDown, focus (если элемент focusable), mouseUp и только теперь, наконец, click.

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

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


getByText('click me')
fireEvent.keyDown(document.activeElement || document.body)


#тестирование #testinglibrary #документация #примерыкода
👍2
Testing Library. Тестирование доступности

Если возникла необходимость затестить доступность вашего приложения, Testing Library предлагает пару полезных утилит:

getRoles

Находит на странице все элементы, имеющие роли, и возвращает их в виде объекта

logRoles

То же самое, только выводит данные в консоль.

isInaccessible

Проверяет, исключен ли элемент из дерева доступности браузера.

Подробнее в документации: https://testing-library.com/docs/dom-testing-library/api-accessibility

#тестирование #testinglibrary #документация
👍1
Testing Library. Debugging

Библиотека предоставляет ряд возможностей для отладки

- Если запросы get или find не находят элементов, то они выбрасывают ошибку, а в консоль выводится DOM корневого элемента (screen или container)
- Утилита prettyDOM принимает элемент и возвращает его структуру
- Метод screen.debug() или screen.debug(element) также получает DOM элемента и выводит его в консоль
- Метод screen.logTestingPlaygroundURL() выводит урл песочницы, в которой уже будет сохранен ваш документ
- Утилита logRoles выводит все элементы, имеющие роли

Подробнее в документации: https://testing-library.com/docs/dom-testing-library/api-debugging

#тестирование #testinglibrary #документация
👍1
Testing Library. within и кастомные запросы

Библиотека также предоставляет ряд низкоуровневых функций для построения более точных/сложных запросов: https://testing-library.com/docs/dom-testing-library/api-custom-queries

И еще есть полезная функция within(element). Она оборачивает полученный элемент и возвращает объект со всеми уже известными нам методами (как у объекта screen). И все эти методы работают "в контексте" полученного элемента: https://testing-library.com/docs/dom-testing-library/api-within

#тестирование #testinglibrary #документация
👍3
user-event

user-event - это библиотека для симуляции пользовательских событий, которую мы можем использовать вместо fireEvent. Можно импортировать из пакета @testing-library/user-event.

В чем разница между user-event и fireEvent?
fireEvent просто диспатчит события DOM, это лишь удобная обертка над методом dispatchEvent.
А user-event пытается имитировать полноценное взаимодействие, которое обычно состоит из нескольких событий.

Простой пример - изменение значения поля ввода.
fireEvent просто задиспатчит событие ‘change’ с нужной строкой, хотя в реальной жизни поле сначала получит фокус, а затем будет ряд событий клавиатуры. user-event пытается все это имитировать, а заодно делает кучу проверок: например, не получится ввести текст в заблокированный инпут.

Настоящий пользователь в браузере вызывает trusted-события, которые нельзя создать программно, а user-event подменяет UI-слой, с которым взаимодействует пользователь в браузере, чтобы это имитировать.

Настройка

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



const user = userEvent.setup() // инициализация
render(…)
await user.click(screen.getByRole('button', {name: /click me!/i}))


В метод можно передать ряд настроек: https://testing-library.com/docs/user-event/options

В результате мы получаем объект user с кучей полезных методов для создания интерактивности.

До
ступные API и методы

👉 type

Для ввода значения в поле ввода.
Первым аргументом принимает само поле, вторым - текст, третим - объект с настройками.

user.type(input, ' World!')


👉 clear

Очистка editable-элемента (фокус, выделение контента, удаление)

user.clear(screen.getByRole('textbox'))


👉 selectOptions/deselectOptions

Работа с селектами или listbox.


user.selectOptions(screen.getByRole('listbox'), ['1', 'C'])


👉 upload

Для загрузки файлов через input[type=«file»]


user.upload(input, new File(['hello'], 'hello.png', {type: 'image/png'}))


👉 clipboard: copy, cut, paste

Работа с буфером обмена. Библиотека подменяет реальный window.navigator.clipboard собственным стабом.
Дока: https://testing-library.com/docs/user-event/clipboard

👉 keyboard

Для симуляции событий клавиатуры, ввода текста. Символы можно указать просто текстом, а также по значениею KeyboardEvent.key или KeyboardEvent.code
Дока: https://testing-library.com/docs/user-event/keyboard

У этого метода также есть шорткат tab (https://testing-library.com/docs/user-event/convenience)


👉 pointer

Работа указателем. Некоторые возможности этого метода не очень актуальны, если вы работаете с jsdom, так как в этом случае реальная отрисовка страницы не происходит.
Дока: https://testing-library.com/docs/user-event/pointer

Это сложный метод, у которого есть много шорткатов (подробнее тут https://testing-library.com/docs/user-event/convenience):

⁃ click
⁃ dblClick
⁃ tripleClick
⁃ hover
⁃ unhover

#тестирование #testinglibrary #документация
👍2
React Testing Library. Первая проба

Наконец-то то, ради чего мы тут и собрались в общем-то. Как использовать Testing Library в React-проектах?

Очень просто, они нам предоставляют обертку @testing-library/react, которая реализует методы для удобной работы с компонентами React.

👀 Тестируем компонент Button вживую: https://codesandbox.io/p/devbox/react-testing-library-react-junior-2yqhll

Сложнее всего тут - настроить окружение, а сами тесты пишутся очень просто и логично.

Итак, нам понадобится:

👉 @testing-library/react - это обертка над @testing-library/dom, поэтому dom нам не нужен.
👉 @testing-library/jest-dom - для добавления полезных утверждений
👉 @testing-library/user-event - для эмуляции пользовательских событий
👉 а также компонент, который нужно протестировать - Button

Тесты в примере очень простые, чтобы разобраться, как это дело заводится.

Первым тестом мы проверяем, что наш компонент в принципе рендерится. Для этого используем метод render из @testing-library/react. Он возвращает объект с уже знакомыми нам методами типа getByRole, findByText и так далее. Соответственно все они работают именно с тем фрагментом, который мы отрендерили.
Находим нашу кнопку по ее тексту (можно и по роли). И утверждаем, что она есть в документе (`expect(button).toBeInTheDocument`).

Второй тест чуть сложнее, он проверяет, что при клике на кнопку вызывает функция-обработчик. Для этого мы создаем поддельную функцию (`jest.fn()`) и передаем ее в компонент в пропе onClick. Опять рендерим, находим кнопку и кликаем на нее, используя метод click из библиотеки user-event (хотя тут можно и нативным кликом обойтись). Наконец, утверждаем, что наша мокнутая функция-обработчик была вызвана (`expect(onClick).toHaveBeenCalled()`).

Все логично, методы удобные, ничего больше не мешает писать тесты))

#тестирование #testinglibrary #документация #примерыкода
👍3
React Testing Library. Пример с сетевыми запросами

Еще один пример тестирования с использованием React Testing Library: https://codesandbox.io/p/sandbox/react-testing-library-fetch-react-junior-vc465v

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

Нам нужно это протестировать: что при клике загружается и отображается картинка.

Алгоритм такой:

⁃ рендерим компонент
⁃ находим кнопку
⁃ кликаем
⁃ ждем, когда на странице появится картинка
⁃ проверяем ее адрес

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

В тесте стоит обратить внимание на две вещи:

👉 мы ждем, когда изображение появится на странице, используя await и find-запрос, который возвращает промис
👉 для утверждения используем метод toHaveAttribute, добавленный библиотекой @testing-library/jest-dom

#тестирование #testinglibrary #документация #примерыкода
👍2
React Testing Library. Кастомный рендер

Мы можем настроить функцию render, передав вторым аргументом объект конфигурации. Например, можно добавить какую-то общую обертку с провайдерми для наших рендеров - параметр wrapper.

#тестирование #testinglibrary #документация #примерыкода
👍2
Render Hook

Помимо render у нас есть еще один метод - renderHook. Как можно догадаться, он предназначен для работы с хуками.


import {renderHook} from '@testing-library/react'

test('returns logged in user', () => {
const {result, rerender} = renderHook((props = {}) => props, {
initialProps: {name: 'Alice'},
})
expect(result.current).toEqual({name: 'Alice'})
rerender()
expect(result.current).toEqual({name: undefined})
})


Метод принимает два аргумента:
- рендер-функцию, которая должна вызвать хук и вернуть результат его работы
- объект конфигурации (опционально)

Результат работы хукуа будет доступен в поле result.current.

#тестирование #testinglibrary #документация #примерыкода
👍2
Руководство по React Testing Library

Статья (англ.): https://www.robinwieruch.de/react-testing-library/

Robin Wieruch по полочкам разложил, что такое RTL и как ей пользоваться.

1. RTL сама по себе не предназначена для организации тестов, поэтому нам в любом случае понадобится один тест-раннеров - Jest или Vitest. С их помощью напишем обертки тестов, а для конкретных действий уже будем использовать RTL.

2. В начале теста рендерим необходимые React-компоненты и сразу же перестаем о них думать как о React-компонентах. Мы должны тестировать обычный DOM, который видит пользователь.

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

Запросы в testing-library

4. Пакет @testing-library/jest-dom добавляет множество удобных утверждений, вроде toBeInTheDocument.

5. Пользовательские события можно эмулировать с помощью fireEvent и user-event.

fireEvent
user-event

6. Разбираемся, как тестировать обработчики событий с помощью мокнутых функций.

7. И наконец учимся работать с асинхронными изменениями, например, сетевыми запросами, с помощью find-методов и waitFor. Не забываем мокать собственно методы, отправляющие запрос (axios/fetch/etc)

waitFor
пример тестирования компонента, выполняющего сетевой запрос


#ссылки #тестирование #testinglibrary
👍2
Testing Library. Полезные ресурсы

- документация Testing Library
- все существующие ARIA-роли для метода getByRole
- список утверждений, которые добавляются библиотекой @testing-library/jest-dom

#ссылки #тестирование #testinglibrary
👍2
Простое руководство по тестированию взаимодействия с пользователем с помощью библиотеки тестирования React

Статья (рус.): https://reddeveloper.ru/blog/363/prostoye-rukovodstvo-po-testirovaniyu-vzaimodeistviya-s-pol-zovatelem-s-pomoshch-yu-biblioteki-testirovaniya-react

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

1. Поле ввода работают правильно

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

Полезные методы:
- getByRole('combobox')
- userEvent.selectOptiions
- expect(select).toHaveValue

2. При вводе/выборе значения оно корректно отображается в интерфейсе

Имеем селект города. Выводим выбранный город где-то в интерфейсе Тестируем, что при выборе города в селекте он изменяется и в интерфейсе.

Полезные методы:
- поиск элемента по тексту getByText
- поиск элемента другими методами, но с уточнением его текста (в настройках метода), например, getByRole('heading', { name: /moscow/i })

3. При ховере появляется элемент

Имеем тултип, который должен появляться при наведении на элемент. Тестируем, что он появляется.

Полезные методы:
- userEvent.hover
- expect(tooltip).toBeInTheDocument

4. Загрузка файлов

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

Полезные методы:
- new File(['hello'], 'hello.png', {type: 'image/png'})
- userEvent.upload(filePicker, file)
- expect(filePicker.files[0]).toEqual(file)

5. Отправка формы

Имеем форму. Тестируем, что при правильном заполнении полей форма успешно отправляется.

Также тестируем все варианты валидации:
- пустые поля
- некорректно заполненные поля

Полезные методы:
- userEvent.type(field, text)
- userEvent.selectOptions(select, option)
- userEvent.click(element)
- getByPlaceholderText(text)
- getByLabelText(text)

#ссылки #тестирование #testinglibrary
👍2