Разработка сайтов в Мариуполе, ДНР. Как тестировать компоненты React с помощью Jest

 
 

В этой статье мы рассмотрим использование Jest — среды тестирования, поддерживаемой Facebook, — для тестирования наших компонентов React. Сначала мы рассмотрим, как мы можем использовать Jest для простых функций JavaScript, а затем рассмотрим некоторые функции, которые он предоставляет из коробки, специально предназначенные для упрощения тестирования приложений React.

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

Шут жонглирует иконками React

Образец заявления

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

Приложение написано на ES2015, скомпилировано с помощью webpack с пресетами Babel ES2015 и React. Я не буду вдаваться в подробности настройки сборки, но все это есть в репозитории GitHub, если вы хотите это проверить. В README вы найдете полные инструкции о том, как запустить приложение локально. Если вы хотите узнать больше, приложение создано с использованием веб- пакета, и я рекомендую «Руководство по веб-пакету для начинающих «в качестве хорошего введения в этот инструмент.

Точкой входа приложения является app/index.js, которая просто отображает Todosкомпонент в HTML:

render (

,

document.getElementById (‘app’)

) ;

Компонент Todosявляется основным центром приложения. Он содержит все состояние (жестко закодированные данные для этого приложения, которые в действительности, скорее всего, будут поступать из API или чего-то подобного) и имеет код для отображения двух дочерних компонентов: Todo, который отображается один раз для каждой задачи в состоянии, и AddTodo, который отображается один раз и предоставляет пользователю форму для добавления новой задачи.

Поскольку Todosкомпонент содержит все состояние, ему нужны компоненты Todoи AddTodoдля уведомления о любых изменениях. Поэтому он передает функции этим компонентам, которые они могут вызывать при изменении некоторых данных, и Todosсоответственно обновлять состояние.

Наконец, на данный момент вы заметите, что вся бизнес-логика содержится в app/state-functions.js:

export function toggleDone (todos, id) {... }

export function addTodo (todos, todo) {... }

export function deleteTodo (todos, id) {... }

Все это чистые функции, которые принимают состояние (которое для нашего примера приложения представляет собой массив задач) и некоторые данные и возвращают новое состояние. Если вы не знакомы с чистыми функциями, то это функции, которые ссылаются только на те данные, которые им предоставлены, и не имеют побочных эффектов. Для получения дополнительной информации вы можете прочитать мою статью в A List Apart о чистых функциях и мою статью на SitePoint о чистых функциях и React.

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

Использовать TDD или не использовать TDD?

Было написано много статей о плюсах и минусах разработки через тестирование, где ожидается, что разработчики сначала напишут тесты, прежде чем писать код для исправления теста. Идея, лежащая в основе этого, заключается в том, что, написав сначала тест, вы должны подумать о API, который вы пишете, и это может привести к лучшему дизайну. Я считаю, что это во многом зависит от личных предпочтений, а также от того, что я тестирую. Я обнаружил, что для компонентов React мне нравится сначала писать компоненты, а затем добавлять тесты к наиболее важным частям функциональности. Однако, если вы обнаружите, что написание тестов в первую очередь для ваших компонентов соответствует вашему рабочему процессу, вам следует это сделать. Здесь нет жесткого правила; делайте то, что лучше для вас и вашей команды.

Представляем бывшего

Впервые Jest был выпущен в 2014 году, и хотя поначалу он вызвал большой интерес, проект какое-то время бездействовал, и над ним не так активно работали. Тем не менее, Facebook приложил много усилий для улучшения Jest и недавно опубликовал несколько выпусков с впечатляющими изменениями, которые заслуживают пересмотра. Единственное сходство Jest с первоначальным выпуском с открытым исходным кодом — это название и логотип. Все остальное изменено и переписано. Если вы хотите узнать об этом больше, вы можете прочитать комментарий Christoph Pojer, где он обсуждает текущее состояние проекта.

Если вы были разочарованы настройкой тестов Babel, React и JSX с использованием другого фреймворка, я определенно рекомендую попробовать Jest. Если вы обнаружили, что ваша существующая тестовая установка работает медленно, я также настоятельно рекомендую Jest. Он автоматически запускает тесты параллельно, а его режим наблюдения может запускать только тесты, относящиеся к измененному файлу, что бесценно, когда у вас большой набор тестов. Он поставляется с настроенным JSDom, что означает, что вы можете писать браузерные тесты, но запускать их через Node. Он может работать с асинхронными тестами и имеет расширенные функции, такие как насмешки, шпионы и встроенные заглушки.

Установка и настройка Jest

Для начала нам нужно установить Jest. Поскольку мы также используем Babel, мы установим еще пару модулей, которые заставят Jest и Babel прекрасно работать из коробки, вместе с Babel и необходимыми пресетами:

npm install —save-dev jest babel-jest @babel/core @babel/preset-env @babel/preset-react

У вас также должен быть babel.config.jsфайл с Babel, сконфигурированный для использования любых пресетов и плагинов, которые вам нужны. В примере проекта уже есть этот файл, который выглядит так:

module.exports = {

presets: [

'@babel/preset-env’,

'@babel/preset-react’,

],

};

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

Мы пока не будем устанавливать никаких инструментов тестирования React, потому что мы начнем не с тестирования наших компонентов, а с наших функций состояния.

Jest рассчитывает найти наши тесты в __tests__папке, которая стала популярной в сообществе JavaScript, и мы собираемся придерживаться ее здесь. Если вы не являетесь поклонником __tests__установки, Jest из коробки также поддерживает поиск любых.test.jsи.spec.jsфайлов.

Поскольку мы будем тестировать наши функции состояния, продолжайте создавать файлы __tests__/state-functions.test.js.

Вскоре мы напишем правильный тест, а пока добавим этот фиктивный тест, который позволит нам проверить, что все работает правильно, и мы настроили Jest:

describe (‘Addition’, () => {

it (‘knows that 2 and 2 make 4', () => {

expect (2 + 2).toBe (4) ;

}) ;

}) ;

Теперь зайдите в свой package.json. Нам нужно настроить npm testтак, чтобы он запускал Jest, и мы можем сделать это, просто настроив testзапуск скрипта jest:

«scripts»: {

«test»: «jest»

}

Если вы теперь запускаете npm testлокально, вы должны увидеть, что ваши тесты выполняются и проходят успешно!

PASS __tests__/state-functions.test.js

Addition

✓ knows that 2 and 2 make 4 (5ms)

Test Suites: 1 passed, 1 total

Tests: 1 passed, 1 total

Snapshots: 0 passed, 0 total

Time: 3.11s

Если вы когда-либо пользовались Jasmine или большинством фреймворков для тестирования, сам приведенный выше тестовый код должен быть вам знаком. Jest позволяет нам использовать describeи itвкладывать тесты по мере необходимости. Сколько вложенности вы используете, зависит от вас. Мне нравится вкладывать свои, чтобы все описательные строки передавались describeи itчитались почти как предложение.

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

Тестирование бизнес-логики

Теперь, когда мы увидели, как Jest работает над фиктивным тестом, давайте запустим его на реальном! Мы собираемся протестировать первую из наших функций состояния, toggleDone. toggleDoneпринимает текущее состояние и идентификатор задачи, которую мы хотим переключить. У каждой задачи есть doneсвойство, и toggleDoneее следует поменять местами с trueна falseили наоборот.

Примечание. Если вы следуете этому, убедитесь, что вы клонировали репозиторий и скопировали appпапку в тот же каталог, в котором находится ваша ___tests__папка. Вам также потребуется установить все зависимости приложения (например, React). Вы можете убедиться, что все это установлено, запустив npm installпосле клонирования репозитория.

Я начну с импорта функции из app/state-functions.jsи настройки структуры теста. В то время как Jest позволяет вам использовать describeи itвкладывать так глубоко, как вы хотите, вы также можете использовать test, что часто будет лучше читаться. test- это просто псевдоним функции Jest it, но иногда он может сделать тесты более легкими для чтения и менее вложенными.

Например, вот как я бы написал этот тест с вложенными вызовами describeи: it

import { toggleDone } from '.../app/state-functions’;

describe (‘toggleDone’, () => {

describe (‘when given an incomplete todo’, () => {

it (‘marks the todo as completed’, () => {

}) ;

}) ;

}) ;

И вот как бы я это сделал test:

import { toggleDone } from '.../app/state-functions’;

test (‘toggleDone completes an incomplete todo’, () => {

}) ;

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

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

import { toggleDone } from «.../app/state-functions»;

test («tooggleDone completes an incomplete todo», () => {

const startState = [{ id: 1, done: false, text: «Buy Milk» }];

const finState = toggleDone (startState, 1) ;

expect (finState).toEqual ([{ id: 1, done: true, text: «Buy Milk» }]) ;

}) ;

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

Теперь мы можем запустить npm testи увидеть, как наш тест функции состояния проходит успешно:

PASS __tests__/state-functions.test.js

✓ tooggleDone completes an incomplete todo (9ms)

Test Suites: 1 passed, 1 total

Tests: 1 passed, 1 total

Snapshots: 0 passed, 0 total

Time: 3.166s

Повторный запуск тестов изменений

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

Чтобы запустить его в режиме просмотра, вы можете запустить npm test — --watch. Все, к чему вы перейдете npm testпосле первого —, будет передано непосредственно базовой команде. Это означает, что эти две команды фактически эквивалентны:

npm test — --watch

jest —watch

Я бы порекомендовал вам оставить Jest запущенным на другой вкладке или в окне терминала до конца этого руководства.

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

Помните, что вам придется обновить importоператор вверху, чтобы импортировать deleteTodoвместе с toggleTodo:

import { toggleDone, deleteTodo } from «.../app/state-functions»;

И вот как я написал тест:

test (‘deleteTodo deletes the todo it is given’, () => {

const startState = [{ id: 1, done: false, text: ‘Buy Milk’ }];

const finState = deleteTodo (startState, 1) ;

expect (finState).toEqual ([]) ;

}) ;

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

Приведенные выше тесты также демонстрируют идеальную компоновку теста, а именно:

настраивать

выполнить тестируемую функцию

утверждать по результатам

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

Теперь, когда мы довольны тестированием наших функций состояния, давайте перейдем к компонентам React.

Тестирование компонентов React

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

Чтобы написать наши тесты, мы установим Enzyme, библиотеку-оболочку, написанную Airbnb, которая значительно упрощает тестирование компонентов React.

Примечание: с тех пор как эта статья была написана впервые, команда React отошла от Enzyme и вместо этого рекомендует React Testing Library (RTL). Стоит прочитать эту страницу. Если вы поддерживаете кодовую базу, в которой уже есть тесты Enzyme, нет необходимости все бросать и уходить, но для нового проекта я бы рекомендовал рассмотреть RTL.

Наряду с Enzyme нам также потребуется установить адаптер для той версии React, которую мы используем. Для React v16 это будет enzyme-adapter-react-16, но для React v17 в настоящее время нет официального адаптера, поэтому нам придется использовать неофициальную версию. Обратите внимание, что этот пакет предназначен в качестве временного решения до тех пор, пока не будет выпущена официальная поддержка, и в это время он будет объявлен устаревшим.

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

npm install —save-dev enzyme @wojtekmaj/enzyme-adapter-react-17

Нам нужно небольшое количество настроек для Enzyme. В корне проекта создайте setup-tests.jsи поместите туда этот код:

import { configure } from ‘enzyme’;

import Adapter from '@wojtekmaj/enzyme-adapter-react-17';

configure ({ adapter: new Adapter () }) ;

Затем нам нужно сказать Jest запустить этот файл для нас, прежде чем будут выполнены какие-либо тесты. Мы можем сделать это, настроив setupFilesAfterEnvопцию. Вы можете поместить конфигурацию Jest в отдельный файл, но мне нравится использовать package.jsonи помещать вещи внутрь jestобъекта, который Jest также подберет:

«jest»: {

«setupFilesAfterEnv»: [

«. /setup-tests.js»

]

}

Теперь мы готовы написать несколько тестов! Давайте проверим, что Todoкомпонент отображает текст своей задачи внутри абзаца. Сначала мы создадим __tests__/todo.test.jsи импортируем наш компонент:

import Todo from '.../app/todo’;

import React from ‘react’;

import { mount } from ‘enzyme’;

test (‘Todo component renders the text of the todo’, () => {

}) ;

Я также импортирую mountиз Enzyme. Функция mountиспользуется для рендеринга нашего компонента, а затем позволяет нам проверять вывод и делать на него утверждения. Несмотря на то, что мы запускаем наши тесты в Node, мы по-прежнему можем писать тесты, требующие DOM. Это связано с тем, что Jest настраивает jsdom, библиотеку, которая реализует DOM в Node. Это здорово, потому что мы можем писать тесты на основе DOM без необходимости каждый раз запускать браузер для их тестирования.

Мы можем использовать mountдля создания нашего Todo:

const todo = { id: 1, done: false, name: ‘Buy Milk’ };

const wrapper = mount (

={todo}>

) ;

А затем мы можем вызвать wrapper.find, предоставив ему селектор CSS, чтобы найти абзац, который, как мы ожидаем, будет содержать текст Todo. Этот API может напомнить вам о jQuery, и это сделано специально. Это очень интуитивно понятный API для поиска совпадающих элементов в визуализированных выходных данных.

const p = wrapper.find ('.toggle-todo’) ;

И, наконец, мы можем утверждать, что текст внутри него Buy Milk:

expect (p.text ()).toBe (‘Buy Milk’) ;

В результате весь наш тест выглядит так:

import Todo from '.../app/todo’;

import React from ‘react’;

import { mount } from ‘enzyme’;

test (‘TodoComponent renders the text inside it’, () => {

const todo = { id: 1, done: false, name: ‘Buy Milk’ };

const wrapper = mount (

={todo}>

) ;

const p = wrapper.find ('.toggle-todo’) ;

expect (p.text ()).toBe (‘Buy Milk’) ;

}) ;

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

Далее давайте посмотрим, как вы можете использовать шпионские функции Jest, чтобы утверждать, что функции вызываются с определенными аргументами. Это полезно в нашем случае, потому что у нас есть Todoкомпонент, которому заданы две функции в качестве свойств, которые он должен вызывать, когда пользователь нажимает кнопку или выполняет взаимодействие.

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

test (‘Todo calls doneChange when todo is clicked’, () => {

}) ;

Нам нужна функция, которую мы можем использовать для отслеживания ее вызовов и аргументов, с которыми она вызывается. Затем мы можем проверить, что когда пользователь нажимает на задачу, doneChangeфункция вызывается и также вызывается с правильными аргументами. К счастью, Jest предлагает это прямо из коробки со шпионами. Шпион — это функция, реализация которой вас не волнует; вы просто заботитесь о том, когда и как это называется. Думайте об этом, как будто вы шпионите за функцией. Чтобы создать его, мы вызываем jest.fn ():

const doneChange = jest.fn () ;

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

const todo = { id: 1, done: false, name: ‘Buy Milk’ };

const doneChange = jest.fn () ;

const wrapper = mount (

={todo}>

) ;

Далее мы можем снова найти наш абзац, как и в предыдущем тесте:

const p = wrapper.find («.toggle-todo») ;

И затем мы можем вызвать simulateего для имитации пользовательского события, передав clickего в качестве аргумента:

p.simulate (‘click’) ;

И все, что осталось сделать, это подтвердить, что наша шпионская функция была вызвана правильно. В этом случае мы ожидаем, что он будет вызван с идентификатором задачи, то есть 1. Мы можем использовать expect (doneChange).toBeCalledWith (1), чтобы подтвердить это — и на этом мы закончили наш тест!

test (‘TodoComponent calls doneChange when todo is clicked’, () => {

const todo = { id: 1, done: false, name: ‘Buy Milk’ };

const doneChange = jest.fn () ;

const wrapper = mount (

={todo}>

) ;

const p = wrapper.find ('.toggle-todo’) ;

p.simulate (‘click’) ;

expect (doneChange).toBeCalledWith (1) ;

}) ;

Вывод

Facebook уже давно выпустил Jest, но в последнее время его подхватили и чрезмерно доработали. Он быстро стал фаворитом для разработчиков JavaScript и будет только улучшаться. Если вы пробовали Jest в прошлом, и он вам не понравился, я не могу убедить вас попробовать его снова, потому что теперь это практически другой фреймворк. Он быстрый, отлично подходит для повторного запуска спецификаций, выдает фантастические сообщения об ошибках и имеет отличный выразительный API для написания хороших тестов.

3D-печать5GABC-анализAndroidAppleAppStoreAsusCall-центрChatGPTCRMDellDNSDrupalExcelFacebookFMCGGoogleHuaweiInstagramiPhoneLinkedInLinuxMagentoMicrosoftNvidiaOpenCartPlayStationPOS материалPPC-специалистRuTubeSamsungSEO-услугиSMMSnapchatSonyStarlinkTikTokTwitterUbuntuUp-saleViasatVPNWhatsAppWindowsWordPressXiaomiYouTubeZoomАвдеевкаАктивные продажиАкцияАлександровск ЛНРАлмазнаяАлчевскАмвросиевкаАнализ конкурентовАнализ продажАнтимерчандайзингАнтрацитАртемовскАртемовск ЛНРАссортиментная политикаБелгородБелицкоеБелозерскоеБердянскБизнес-идеи (стартапы)БрендБрянкаБукингВахрушевоВендорВидеоВикипедияВирусная рекламаВирусный маркетингВладивостокВнутренние продажиВнутренний маркетингВолгоградВолновахаВоронежГорловкаГорнякГорскоеДебальцевоДебиторкаДебиторская задолженностьДезинтермедитацияДзержинскДивизионная система управленияДизайнДимитровДирект-маркетингДисконтДистрибьюторДистрибьюцияДобропольеДокучаевскДоменДружковкаЕкатеринбургЕнакиевоЖдановкаЗапорожьеЗимогорьеЗолотоеЗоринскЗугрэсИжевскИловайскИрминоКазаньКалининградКировскКировскоеКомсомольскоеКонстантиновкаКонтент-маркетингКонтент-планКопирайтингКраматорскКрасноармейскКрасногоровкаКраснодарКраснодонКраснопартизанскКрасный ЛиманКрасный ЛучКременнаяКураховоКурскЛисичанскЛуганскЛутугиноМакеевкаМариупольМаркетингМаркетинговая информацияМаркетинговые исследованияМаркетинговый каналМаркетинг услугМаркетологМарьинкаМедиаМелекиноМелитопольМенеджментМерчандайзерМерчандайзингМиусинскМолодогвардейскМоскваМоспиноНижний НовгородНиколаевНиколаевкаНишевой маркетингНовоазовскНовогродовкаНоводружескНовосибирскНумерическая дистрибьюцияОдессаОмскОтдел маркетингаПартизанский маркетингПервомайскПеревальскПетровскоеПлата за кликПоисковая оптимизацияПопаснаяПравило ПаретоПривольеПрогнозирование продажПродвижение сайтов в ДонецкеПроизводство видеоПромоПромоушнПрямой маркетингРабота для маркетологаРабота для студентаРазработка приложенийРаспродажаРегиональные продажиРекламаРеклама на асфальтеРемаркетингРетро-бонусРибейтРитейлРовенькиРодинскоеРостов-на-ДонуРубежноеСамараСанкт-ПетербургСаратовСватовоСвердловскСветлодарскСвятогорскСевастопольСеверодонецкСеверскСедовоСейлз промоушнСелидовоСимферопольСинергияСколковоСлавянскСнежноеСоздание сайтов в ДонецкеСоледарСоциальные сетиСочиСтаробельскСтаробешевоСтахановСтимулирование сбытаСуходольскСчастьеТелемаркетингТельмановоТираспольТорговый представительТорезТрейд маркетингТрейд промоушнТюменьУглегорскУгледарУкраинскХабаровскХарцызскХерсонХостингЦелевая аудиторияЦифровой маркетингЧасов ЯрЧелябинскШахтерскЮжно-СахалинскЮнокоммунаровскЯндексЯсиноватая