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

RedDeveloper
29.01.2023 12:32
Простое руководство по тестированию взаимодействия с пользователем с помощью библиотеки тестирования React

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

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

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

Настройка

Приложение с помощью команды create-react-app testing-user-interactions. Далее установите библиотеку user-event.

npm i user-event

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

Каждого теста вам нужно будет выполнить следующие импорты.

import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'

Кроме того, импортируйте компонент, который вы будете тестировать.

Как протестировать взаимодействие элементов выбора?

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

<select id='selectCity'>
    <option> Mumbai</option>
    <option> Bangalore</option>
    <option> Chennai</option>
</select>

Рассмотрим, как сделать запрос к элементу. Вы можете сделать screen.getByRole('combobox'). См. список ролей здесь .

Если есть несколько элементов select, вы можете использовать getByLabelText(). Для этого добавьте метку, <label htmlFor = "selectCity">Select City</label> для элемента и выполните screen.getByLabelText(/select city/i). Вы получите элемент.

Используйте метод selectOptions() библиотеки для имитации выбора пользователем опции. Он принимает три аргумента: элемент, значение и любые опции.

userEvent.selectOptions(
    screen.getByRole('combobox'),
    'Chennai'
)

Затем сделайте утверждение.

expect(screen.getByRole('combobox')).toHaveValue('Chennai')

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

Тестирование изменения состояния

Я сделаю обновление состояния при изменении значения элемента select. Сначала у меня будет состояние cityName для отображения выбранного варианта.

const [cityName, setCityName] = useState('Mumbai');
<h2> City Name:  {cityName} </h2>

Затем я добавлю атрибут onChange с методом, который обновляет состояние.

onChange = {(e) => { setCityName(e.target.value)}}

Чтобы написать тест для этого, следуйте тому же процессу.

test("City Name header rendering", () => {
    render(<SelectElement/>)
    userEvent.selectOptions(
        screen.getByRole('combobox'),
        'Bangalore'
    )
    expect(screen.getByRole('heading', 
              { name: /bangalore/i})).toBeInTheDocument();
})  

В конце сделайте утверждение, запросив элемент h2 с помощью getByRole() и параметра name.

Событие наведения

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

<div className = "tooltip">
    <p> Hover over me</p>
    <span className = "tooltiptext">Tooltip text</span>
</div>

Есть также некоторые CSS стили для классов, определенных выше, но я не буду показывать их здесь. В принципе, когда пользователь наводит курсор на текст Hover over me, текст всплывающей подсказки становится видимым.

Чтобы имитировать наведение пользователя на элемент, используйте метод hover() и сделайте утверждение.

test("Tooltip visible", () => {
    render(<HoverEvent/>)
    userEvent.hover(screen.getByText(/hover over me/i))
    expect(screen.getByText(/tooltip text/i)).toBeInTheDocument()
})

Это все, что вам нужно сделать.

Тестирование события загрузки файлов

Еще одно важное событие - это загрузка пользователем файлов на ваш сайт. Это довольно распространенная функциональность в любом UI-приложении.

Пусть у нас есть вход типа file.

<label htmlFor = "singleFile">Upload File</label>
<input type='file' id='singleFile' />

Теста, вам нужно будет имитировать загрузку файла пользователем. Для этого используйте метод upload().

test("Test single file upload", () => {
    render(<FileUpload/>)
    const file = new File(['hello'], 'hello.png', {type: 'image/png'})
    userEvent.upload(screen.getByLabelText(/upload file/i), file)
    expect(screen.getByLabelText(/upload file/i).files[0]).toEqual(file);
})

Сначала создайте макет файла и загрузите его. Затем сделайте утверждение, что файл, присутствующий во входных данных, совпадает с нашим загруженным файлом. Так и есть, поэтому тест пройден.

Теперь давайте рассмотрим пример с загрузкой нескольких файлов.

<label htmlFor='multipleFiles'>Upload multiple files</label>
<input type='file' id='multipleFiles' multiple />

В нашем тесте загрузите несколько файлов.

test("Test multiple file uploads", () => {
    render(<FileUpload/>)
    const files = [
        new File(['file1'], 'file1.png', {type: 'image/png'}),
        new File(['file2'], 'file2.png', {type: 'application/pdf'})
    ]
    userEvent.upload(screen.getByLabelText(/upload multiple files/i), files)
    const fileInput = screen.getByLabelText(/upload multiple files/i)
    expect(fileInput.files.length).toBe(2);
    expect(fileInput.files[0]).toEqual(files[0]);
    expect(fileInput.files[1]).toEqual(files[1]);
})

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

Теперь время для испытания. Просмотрите мои посты Muliple File Uploads и Different Examples of the useState Hook и напишите модульные тесты для всех функций. Загрузите свой код на GitHub и поделитесь им со мной.

Поведение формы

Важной особенностью большинства веб-приложений является работа с формами. При написании тестов для React-приложения вам, скорее всего, понадобится проверить поведение компонента с формами.

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

const [name, setName] = useState('')
const [password, setPassword] = useState('')
const [country, setCountry] = useState('')
const [formSubmitted, setFormSubmitted] = useState(false)
<form>
    <input onChange = {(e) => {setName(e.target.value)}} 
            placeholder='Enter name'/>
    <input onChange = {(e) => {setPassword(e.target.value)}} 
            placeholder='Enter password'/>
    <label htmlFor='selectCountry'> Select Country </label>
    <select id='selectCountry' 
            onChange = {(e) => {setCountry(e.target.value)}}>
        <option>India</option>
        <option>Japan</option>
        <option>China</option>
        <option>USA</option>
        <option>England</option>
    </select>
    <button onClick = {handleSubmit}> Submit </button>
</form>

В атрибуте onChange выполните обновление состояний. В конце выведите текст об успешном отправлении формы.

{formSubmitted && 'Form Submitted'}

Давайте добавим некоторую валидацию в метод handleSubmit. Когда валидация не пройдет, будет показан список ошибок. Создайте еще одно состояние для хранения ошибок.

const [errors, setErrors] = useState([])

Теперь реализуйте метод handleSubmit.

const handleSubmit = (event) => {
    event.preventDefault();
    const errorList=[];
    if (name == '' || password == '' || country == '') {
        errorList.push('Please fill all the details')
    }
    
    const regex = /^[a-z]*$/i;
    if (!name.match(regex)) {
        errorList.push('Please enter a valid name')
    }

    if (errorList.length != 0) {
        setErrors(errorList);
        setFormSubmitted(false);
        return;  
    } 
    setErrors([]);
    setFormSubmitted(true)
}

Сначала мы выполним event.preventDefault(), который отменит все события, препятствующие отправке формы. Затем мы добавим несколько проверок на ошибки и добавим все ошибки в состояние errors. В конце, если ошибок нет, установим errors в пустой массив и установим formSubmitted как true.

Мы также выведем список всех ошибок, который будет виден при нажатии на кнопку submit.

errors.map(err => (
    <p>
        {err}
    </p>
))

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

test("Form getting submitted with correct input values", () => {
    render(<FormBehaviour/>)

    userEvent.type(screen.getByPlaceholderText(/enter name/i), 'kunal')
    userEvent.type(screen.getByPlaceholderText(/enter password/i), 'pass')
    userEvent.selectOptions(screen.getByLabelText(/select country/i), 'India')
    userEvent.click(screen.getByText(/submit/i))

    expect(screen.getByText(/form submitted/i)).toBeInTheDocument();
})

Методы type и click библиотеки userEvent для имитации соответствующих действий. В конце добавьте утверждение, чтобы проверить, виден ли текст, соответствующий regex /form submitted/i, в документе, поскольку наша логика отображает текст, как только форма отправлена.

методы type и click библиотеки userEvent для имитации соответствующих действий В конце

Тест пройден, но все еще есть несколько незакрытых строк. Мы протестировали поведение нормальной формы. Однако у нас также есть механизмы обработки ошибок. Поэтому вам также необходимо написать тесты, которые охватывают различные сценарии ошибок.

Сначала имитируйте, что пользователь нажимает кнопку отправки, ничего не вводя.

test("Empty fields error shown", () => {
    render(<FormBehaviour/>)
    userEvent.click(screen.getByText(/submit/i))
    expect(screen.getByText(/please fill all the details/i))
            .toBeInTheDocument()
}) 

При нажатии на кнопку отправки должно появиться сообщение об ошибке, предлагающее пользователю заполнить все данные. Сделайте утверждение для этого.

Еще не все строчки рассмотрены. У нас остался еще один сценарий ошибки. Что если пользователь введет неправильное имя?

test("Invalid name error shown", () => {
    render(<FormBehaviour/>)
    userEvent.type(screen.getByPlaceholderText(/enter name/i), '@###')
    userEvent.type(screen.getByPlaceholderText(/enter password/i), 'pass')
    userEvent.selectOptions(screen.getByLabelText(/select country/i), 'India')
    userEvent.click(screen.getByText(/submit/i))
    expect(screen.getByText(/Please enter a valid name/i)).toBeInTheDocument();
})

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

Этот тест похож на первый тест только здесь пользователь вводит недопустимое имя Поэтому

Теперь все наши тесты проходят со 100% покрытием.

Вы можете найти полный код на GitHub .

Заключение

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

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

Laravel с Turbo JS
Laravel с Turbo JS

29.03.2023 12:59

Turbo - это библиотека JavaScript для упрощения создания быстрых и высокоинтерактивных веб-приложений. Она работает с помощью техники под названием "Turbo Links", которая позволяет перемещаться между страницами сайта без полной перезагрузки страницы.

Типы ввода HTML: Лучшие практики и советы
Типы ввода HTML: Лучшие практики и советы

29.03.2023 12:29

HTML, или HyperText Markup Language , является стандартным языком разметки, используемым для создания веб-страниц. Типы ввода HTML - это различные типы элементов управления формами, которые могут использоваться для сбора информации от пользователей на веб-страницах. Существует множество различных...

Аутсорсинг разработки PHP для индивидуальных веб-решений
Аутсорсинг разработки PHP для индивидуальных веб-решений

29.03.2023 09:49

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

Понимание Python и переход к SQL
Понимание Python и переход к SQL

28.03.2023 13:50

Перед нами лабораторная работа по BloodOath:

Слишком много useState? Давайте useReducer!
Слишком много useState? Давайте useReducer!

28.03.2023 12:46

Современный фронтенд похож на старую добрую веб-разработку, но с одной загвоздкой: страница в браузере так же сложна, как и бэкенд.

Узнайте, как использовать теги &lt;ul&gt; и &lt;li&gt; для создания неупорядоченных списков в HTML
Узнайте, как использовать теги <ul> и <li> для создания неупорядоченных списков в HTML

28.03.2023 10:02

HTML предоставляет множество тегов для структурирования и организации содержимого веб-страницы. Одним из наиболее часто используемых тегов для отображения списков является тег <ul>. В этом уроке мы рассмотрим, как использовать теги <ul> и <li> для создания неупорядоченных списков на веб-странице.