Пошук уроків, статей та іншого контенту
Протестуєте кліки, введення тексту, вибір опцій та інші дії користувача через user-event.
Інтерактивний React-компонент змінює стан у відповідь на дії користувача:
клік по кнопці;
введення тексту;
вибір опції у списку;
встановлення прапорця;
натискання клавіш;
надсилання форми.
Тест має перевіряти не внутрішній стан компонента, а результат дії з погляду користувача. Наприклад:
після введення тексту значення видно в полі;
після вибору опції вибрана саме потрібна опція;
після кліку по прапорцю він встановлений;
після надсилання форми з’явилося повідомлення.
Для симуляції дій користувача використовується @testing-library/user-event.
userEvent замість fireEventfireEvent безпосередньо створює DOM-подію:
fireEvent.click(button);userEvent імітує реальнішу послідовність дій користувача. Наприклад, введення тексту може містити фокусування елемента, події клавіатури та зміну значення:
await user.type(input, 'Олена');Для сучасних тестів зазвичай потрібно:
створити користувача через userEvent.setup();
використовувати await перед діями користувача.
const user = userEvent.setup();
await user.click(button);
await user.type(input, 'Текст');Це важливо, оскільки дії userEvent можуть виконуватися асинхронно.
Розглянемо форму налаштувань користувача:
import { useState } from 'react';
export function PreferencesForm() {
const [name, setName] = useState('');
const [color, setColor] = useState('blue');
const [newsletter, setNewsletter] = useState(false);
const [submittedName, setSubmittedName] = useState('');
function handleSubmit(event) {
event.preventDefault();
setSubmittedName(name);
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Ім’я</label>
<input
id="name"
value={name}
onChange={(event) => setName(event.target.value)}
/>
<label htmlFor="color">Улюблений колір</label>
<select
id="color"
value={color}
onChange={(event) => setColor(event.target.value)}
>
<option value="blue">Синій</option>
<option value="green">Зелений</option>
<option value="red">Червоний</option>
</select>
<label>
<input
type="checkbox"
checked={newsletter}
onChange={(event) => setNewsletter(event.target.checked)}
/>
Отримувати новини
</label>
<button type="submit" disabled={!name.trim()}>
Зберегти
</button>
{submittedName && (
<p role="status">Налаштування збережено для: {submittedName}</p>
)}
</form>
);
}Для пошуку елементів використовуються доступні ролі та назви:
textbox — текстове поле;
combobox — елемент select;
checkbox — прапорець;
button — кнопка;
status — повідомлення про результат.
Щоб перевірити введення тексту:
знайдіть поле за доступною назвою;
викличте user.type;
перевірте його значення.
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect, it } from 'vitest';
import { PreferencesForm } from './PreferencesForm';
it('дозволяє ввести ім’я', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
const nameInput = screen.getByRole('textbox', { name: 'Ім’я' });
await user.type(nameInput, 'Олена');
expect(nameInput).toHaveValue('Олена');
});toHaveValue перевіряє поточне значення поля, а не текст у DOM поруч із ним.
Для очищення значення використовується user.clear:
it('очищає введене ім’я', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
const nameInput = screen.getByRole('textbox', { name: 'Ім’я' });
await user.type(nameInput, 'Олена');
await user.clear(nameInput);
expect(nameInput).toHaveValue('');
});Клік перевіряється через user.click:
it('активує кнопку після введення імені', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
const nameInput = screen.getByRole('textbox', { name: 'Ім’я' });
const saveButton = screen.getByRole('button', { name: 'Зберегти' });
expect(saveButton).toBeDisabled();
await user.type(nameInput, 'Олена');
expect(saveButton).toBeEnabled();
});Спочатку кнопка вимкнена, оскільки ім’я порожнє. Після введення тексту React оновлює стан, і кнопка стає доступною.
Перевірки toBeDisabled та toBeEnabled надає пакет @testing-library/jest-dom.
Для прапорця потрібно перевірити його стан через toBeChecked:
it('дозволяє підписатися на новини', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
const newsletterCheckbox = screen.getByRole('checkbox', {
name: 'Отримувати новини',
});
expect(newsletterCheckbox).not.toBeChecked();
await user.click(newsletterCheckbox);
expect(newsletterCheckbox).toBeChecked();
await user.click(newsletterCheckbox);
expect(newsletterCheckbox).not.toBeChecked();
});Повторний клік по прапорцю змінює його стан на протилежний.
Для select використовується user.selectOptions:
it('дозволяє вибрати улюблений колір', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
const colorSelect = screen.getByRole('combobox', {
name: 'Улюблений колір',
});
expect(colorSelect).toHaveValue('blue');
await user.selectOptions(colorSelect, 'green');
expect(colorSelect).toHaveValue('green');
expect(screen.getByRole('option', { name: 'Зелений' })).toBeSelected();
});У selectOptions передається значення атрибута value, а не обов’язково текст, який бачить користувач:
<option value="green">Зелений</option>Тому в тесті використовується 'green'.
Користувач спочатку вводить дані, а потім натискає кнопку. Тест перевіряє кінцевий результат:
it('надсилає форму після кліку по кнопці', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
await user.type(
screen.getByRole('textbox', { name: 'Ім’я' }),
'Олена',
);
await user.click(
screen.getByRole('button', { name: 'Зберегти' }),
);
expect(screen.getByRole('status')).toHaveTextContent(
'Налаштування збережено для: Олена',
);
});Такий тест не перевіряє, чи викликався конкретний обробник handleSubmit. Він перевіряє поведінку, важливу для користувача: після надсилання з’явилося повідомлення.
userEvent також дозволяє імітувати натискання клавіш через keyboard.
Наприклад, користувач може перейти до кнопки клавішею Tab і натиснути Enter:
it('надсилає форму клавішею Enter', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
await user.type(
screen.getByRole('textbox', { name: 'Ім’я' }),
'Олена',
);
await user.tab();
await user.keyboard('{Enter}');
expect(screen.getByRole('status')).toHaveTextContent(
'Налаштування збережено для: Олена',
);
});Такий тест допомагає перевіряти доступний сценарій взаємодії без миші.
Нижче наведено повний файл тестів для компонента PreferencesForm:
import '@testing-library/jest-dom/vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it } from 'vitest';
import { PreferencesForm } from './PreferencesForm';
describe('PreferencesForm', () => {
it('дозволяє ввести ім’я', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
const nameInput = screen.getByRole('textbox', { name: 'Ім’я' });
await user.type(nameInput, 'Олена');
expect(nameInput).toHaveValue('Олена');
});
it('дозволяє вибрати колір', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
const colorSelect = screen.getByRole('combobox', {
name: 'Улюблений колір',
});
await user.selectOptions(colorSelect, 'green');
expect(colorSelect).toHaveValue('green');
expect(screen.getByRole('option', { name: 'Зелений' }))
.toBeSelected();
});
it('змінює стан прапорця після кліку', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
const checkbox = screen.getByRole('checkbox', {
name: 'Отримувати новини',
});
expect(checkbox).not.toBeChecked();
await user.click(checkbox);
expect(checkbox).toBeChecked();
});
it('зберігає ім’я після надсилання форми', async () => {
const user = userEvent.setup();
render(<PreferencesForm />);
await user.type(
screen.getByRole('textbox', { name: 'Ім’я' }),
'Олена',
);
await user.click(
screen.getByRole('button', { name: 'Зберегти' }),
);
expect(screen.getByRole('status')).toHaveTextContent(
'Налаштування збережено для: Олена',
);
});
});awaitНеправильно:
user.click(button);
expect(screen.getByRole('status')).toBeInTheDocument();Правильно:
await user.click(button);
expect(screen.getByRole('status')).toBeInTheDocument();Дії userEvent потрібно очікувати, інакше перевірка може виконатися до оновлення компонента.
Небажано:
await userEvent.setup().click(button);
await userEvent.setup().type(input, 'Текст');Краще створити один екземпляр на тест:
const user = userEvent.setup();
await user.click(button);
await user.type(input, 'Текст');Так зберігається послідовний контекст взаємодії користувача.
Компонент може мати стан isChecked, але тест не повинен отримувати його напряму. Перевіряйте результат у DOM:
expect(checkbox).toBeChecked();Так тест залишається незалежним від реалізації компонента.
Небажано:
screen.getByTestId('name-input');Якщо елемент має доступну назву, краще:
screen.getByRole('textbox', { name: 'Ім’я' });Такий пошук ближчий до того, як користувач взаємодіє зі сторінкою, і водночас перевіряє доступність розмітки.
Для полів введення використовуйте toHaveValue:
expect(input).toHaveValue('Олена');toHaveTextContent перевіряє текстовий вміст елемента і не підходить для перевірки значення input.
userEvent імітує реальні дії користувача.
Для кожного тесту створюйте const user = userEvent.setup().
Перед діями click, type, clear, selectOptions, tab та keyboard використовуйте await.
Для введення тексту використовуйте user.type.
Для кліків — user.click.
Для прапорців перевіряйте toBeChecked.
Для списків select використовуйте user.selectOptions.
Для значення полів використовуйте toHaveValue.
Перевіряйте видимий результат взаємодії, а не внутрішній стан React-компонента.
Для пошуку елементів надавайте перевагу доступним ролям і назвам.