- Разберемся, что не нужно тестировать
- Разумное использование снапшотов
- Делайте ваши тесты понятными
- Краткость — сестра таланта
- Моки ответов сервера
- Автоматизация
На своих проектах я использую как unit, так и интеграционные тесты. В последние пару месяцев я написал много unit-тестов для React компонентов и хочу поделиться некоторыми простыми концепциями. Они помогут вам писать правильные unit-тесты, которые будет легко поддерживать.
Названия функций и примеры ниже написаны на Jest и RTL (RTL — React testing library), но концепции применимы к любой другой библиотеке для тестирования фронтенда.
Разберемся, что не нужно тестировать
Самый важный мой совет не про то, как тестировать, а про то, что тестировать не нужно. Он применим к ситуациям, когда на вашем проекте есть или планируются E2E интеграционные тесты. Важно понимать, что многие use-кейсы могут (и должны) покрываться интеграционными тестами, а не unit-тестами.
Не все фичи могут быть протестированы интеграционными тестами. Например, если для теста нужно создавать 3-4 мока, во время теста изменяется состояние приложения — лучше делать интеграцию. Но для простых тестов атомарной функциональности unit-тестирование подойдет идеально. Вот еще пара примеров:
❌ Пример плохого сценария для unit-теста:
- Проверка формы логина (запросы и ответы от сервера, возможность увидеть следующую страницу после логина в приложение)
✅ Примеры хороших сценариев для unit-теста:
- Добавление нового <option> в <select> и проверка того, что он показывается
- Добавление click ивента на кнопку и проверка того, что вызывается callback
Разумное использование снапшотов
Снапшот тестирование помогает отслеживать неожиданные изменения в компонентах. Но не нужно путать снапшот-тестирование с нормальными функциональными тестами.
Правильный сценарий использования снапшотов — отслеживать изменения в shared-компонентах. Когда в такой компонент вносятся изменения — снапшоты покажут все места, на которые эти изменения повлияли. Но это единственное, что делают снапшоты. После того, как снапшоты упали, вам все равно придется вручную проверить все эти места и убедиться, что изменение ничего не сломало.
Делайте ваши тесты понятными
Обязанность разработчика — писать чистый и понятный код, который будет легко читать другим разработчикам.
У Jest классный синтаксис, который помогает делать тесты легко читаемыми и понятными. Используйте его!
❌ Пример непонятного теста:
describe('component', () => { it('performs correctly', () => { … }); });
✅ Пример понятного теста:
describe('the admin page', () => { describe('when a user is not logged in', () => { it('shows a login button', () => { … }); }); });
Обратите внимание, в последнем примере тест читается, как нормальное предложение. Когда тест упадет, вам сразу будет понятно, что конкретно не работает и в каком направлении копать.
Краткость — сестра таланта
Делайте каждый ваш тест настолько маленьким, насколько это возможно. Придерживайтесь принципов DRY, вон несколько правил, которым нужно следовать:
- Если несколько тестов используют один и тот же код, выносите его в beforeEach и afterEach.
- Когда нужно тестировать один и тот же компонент, рендерите его один раз в beforeEach.
- Если какие-нибудь значения используются и в компоненте и в тесте, выносите их в константы и импортируйте в тест и компонент.
- Если один и тот же мок используется в нескольких тестах, выносите его в отдельное место и импортируйте в ваши тесты.
- Используйте ID вместо привязки к структуре DOM или к тексту внутри компонентов. Структура dom-дерева и текст, в отличие от id, могут часто меняться. Если вы хотите использовать другой формат (вместо data-testid, который рекомендует RTL), его можно сменить в конфиге RTL. Я, например, использую data-test.
Моки ответов сервера
Моки для ответов API позволяют протестировать компонент, подключенный к данным. После появления хуков, стало гораздо проще вынести логику асинхронных запросов из компонента и, соответственно, писать моки для компонентов.
Я использовал библиотеку @react-mock/fetch. С ее помощью можно очень легко сделать мок для HTTP-запросов, данные от которых получает компонент. Нужно просто обернуть ваш компонент в <FetchMock> и валя — данные внутри компонента:
import { FetchMock } from '@react-mock/fetch'; const mockedResponse = { matcher: '/ay', method: 'GET', response: JSON.stringify({ body: 'yo' }) }; render( <FetchMock options={mockedResponse}> <MyComponent /> </FetchMock> );
Автоматизация
Вот, как я запускаю свои тесты:
- Использую pre-push хук Husky, который запускает тесты перед пушем в репозиторий
- Jenkins merge build прогоняет тесты в каждом PR перед тем, как смержить его в главную ветку
Вам решать, как организовать CI, но рекомендую сделать хотя бы что-то одно из тех двух опций выше. Это поможет получить от unit-тестов максимальную пользу.