Пошук уроків, статей та іншого контенту
Навчитеся знаходити елементи через доступні для користувача Queries та перевіряти результат за допомогою Assertions.
У тестах React Testing Library є два основні кроки:
Знайти елемент на сторінці за допомогою Query.
Перевірити результат за допомогою Assertion.
Наприклад, тест може знайти кнопку, натиснути її та перевірити, що після цього з’явився текст:
const button = screen.getByRole('button', { name: 'Показати повідомлення' });
await user.click(button);
expect(screen.getByText('Операцію виконано')).toBeInTheDocument();Такий тест перевіряє поведінку застосунку з точки зору користувача, а не його внутрішню реалізацію.
Query — це метод для пошуку елемента в DOM, який створився після рендерингу компонента.
Найчастіше використовується об’єкт screen:
import { render, screen } from '@testing-library/react';
render(<Greeting />);
const heading = screen.getByRole('heading', { name: 'Вітаємо' });Замість пошуку за CSS-класами або внутрішніми деталями компонента потрібно шукати елементи так, як їх може сприйняти користувач:
кнопку — за її роллю та назвою;
поле введення — за підписом;
заголовок — за роллю та текстом;
повідомлення — за видимим текстом.
getBy...Методи getBy... використовують, коли елемент уже має бути присутнім у DOM.
Якщо елемент:
не знайдено — тест одразу завершується помилкою;
знайдено більше одного — тест також завершується помилкою.
Найкращий варіант для більшості інтерактивних елементів — getByRole.
screen.getByRole('button', { name: 'Зберегти' });
screen.getByRole('heading', { name: 'Профіль користувача' });
screen.getByRole('checkbox', { name: 'Отримувати сповіщення' });Роль описує тип елемента, а name — його доступну назву.
Наприклад, для такого компонента:
<button type="button">Зберегти</button>доступна назва кнопки — Зберегти.
Для пошуку без урахування регістру можна використати регулярний вираз:
screen.getByRole('button', { name: /зберегти/i });Прапорець i означає, що регістр символів не має значення.
Для полів форми зручно використовувати getByLabelText:
<label htmlFor="email">Електронна пошта</label>
<input id="email" type="email" />const emailInput = screen.getByLabelText('Електронна пошта');Зв’язок між label та input створюється завдяки однаковим значенням htmlFor і id.
getByText знаходить елемент за видимим текстом:
screen.getByText('Профіль збережено');Цей Query підходить для повідомлень, заголовків або іншого статичного тексту. Для кнопок та інших інтерактивних елементів зазвичай краще використовувати getByRole.
queryBy...Методи queryBy... схожі на getBy..., але повертають null, якщо елемент не знайдено.
Це корисно, коли потрібно перевірити, що елемент відсутній:
expect(screen.queryByText('Помилка')).not.toBeInTheDocument();Якщо використати getByText у такій ситуації, сам пошук завершиться помилкою ще до виконання Assertion:
// Неправильний варіант для перевірки відсутності
expect(screen.getByText('Помилка')).not.toBeInTheDocument();queryBy... також викидає помилку, якщо знайдено кілька елементів. Для перевірки кількох результатів використовують queryAllBy....
findBy...Методи findBy... використовують для елементів, які з’являються асинхронно, наприклад після відповіді сервера або завершення таймера.
Такий Query повертає Promise, тому перед ним потрібно використати await:
const message = await screen.findByText('Дані завантажено');
expect(message).toBeInTheDocument();findBy... чекає певний час на появу елемента. Якщо елемент так і не з’явиться, тест завершиться помилкою.
Для кожного способу пошуку є кілька варіантів:
| Варіант | Коли використовувати | |---|---| | getBy... | Елемент має бути присутнім одразу | | queryBy... | Перевіряємо, що один елемент відсутній | | findBy... | Елемент з’являється асинхронно | | getAllBy... | Очікуємо кілька елементів одразу | | queryAllBy... | Перевіряємо список елементів, який може бути порожнім | | findAllBy... | Очікуємо появу кількох елементів асинхронно |
Наприклад:
const items = screen.getAllByRole('listitem');
expect(items).toHaveLength(3);Під час написання тесту варто починати з найбільш доступного для користувача способу пошуку:
getByRole
getByLabelText
getByPlaceholderText
getByText
getByDisplayValue
getByAltText
getByTestId
getByPlaceholderTextЦей Query шукає поле за текстом у placeholder:
<input placeholder="Введіть ім’я" />const input = screen.getByPlaceholderText('Введіть ім’я');Однак placeholder не замінює доступний підпис поля. Для важливих полів краще використовувати label і getByLabelText.
getByTestIdgetByTestId шукає елемент за атрибутом data-testid:
<div data-testid="loading-indicator">Завантаження...</div>screen.getByTestId('loading-indicator');Цей спосіб варто використовувати лише тоді, коли елемент неможливо надійно знайти через його роль, текст або підпис. data-testid описує деталь реалізації, а не те, як користувач взаємодіє зі сторінкою.
Assertion — це перевірка очікуваного результату.
У тестах зазвичай використовується конструкція:
expect(actualValue).matcher(expectedValue);Наприклад:
expect(screen.getByRole('heading')).toBeInTheDocument();Тут:
expect(...) отримує значення, яке потрібно перевірити;
toBeInTheDocument() перевіряє, що елемент є в DOM.
toBeInTheDocumentПеревіряє наявність елемента в DOM:
expect(screen.getByText('Готово')).toBeInTheDocument();Для перевірки відсутності використовується .not:
expect(screen.queryByText('Помилка')).not.toBeInTheDocument();toHaveTextContentПеревіряє текст усередині елемента:
const message = screen.getByRole('status');
expect(message).toHaveTextContent('Зміни збережено');toBeDisabledПеревіряє, що кнопка або інший елемент форми вимкнений:
expect(screen.getByRole('button', { name: 'Надіслати' }))
.toBeDisabled();toBeEnabledПеревіряє, що елемент доступний для взаємодії:
expect(screen.getByRole('button', { name: 'Надіслати' }))
.toBeEnabled();toHaveValueПеревіряє значення поля:
const input = screen.getByLabelText('Ім’я');
expect(input).toHaveValue('Олена');toBeCheckedПеревіряє стан прапорця або перемикача:
expect(screen.getByRole('checkbox', { name: 'Погоджуюся' }))
.toBeChecked();У цьому прикладі компонент спочатку показує кнопку. Після натискання кнопки з’являється повідомлення.
import { useState } from 'react';
export function SaveMessage() {
const [saved, setSaved] = useState(false);
return (
<section>
<h1>Налаштування</h1>
<button type="button" onClick={() => setSaved(true)}>
Зберегти
</button>
{saved && (
<p role="status">
Налаштування збережено
</p>
)}
</section>
);
}Тест для цього компонента:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it } from 'vitest';
import '@testing-library/jest-dom/vitest';
import { SaveMessage } from './SaveMessage';
describe('SaveMessage', () => {
it('показує повідомлення після натискання кнопки', async () => {
const user = userEvent.setup();
render(<SaveMessage />);
const saveButton = screen.getByRole('button', {
name: 'Зберегти',
});
expect(screen.queryByRole('status')).not.toBeInTheDocument();
await user.click(saveButton);
expect(screen.getByRole('status')).toHaveTextContent(
'Налаштування збережено',
);
});
});Послідовність тесту:
Створюється користувач за допомогою userEvent.
Компонент рендериться через render.
Кнопка знаходиться за роллю та доступною назвою.
Перевіряється, що повідомлення спочатку відсутнє.
Виконується клік.
Перевіряється текст повідомлення.
Тест не перевіряє значення стану saved напряму. Він перевіряє видимий результат, який побачить користувач.
Один тест може містити кілька пов’язаних перевірок:
it('відображає початковий стан форми', () => {
render(<LoginForm />);
expect(
screen.getByRole('heading', { name: 'Вхід' }),
).toBeInTheDocument();
expect(screen.getByLabelText('Електронна пошта')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Увійти' }))
.toBeDisabled();
});Ці Assertions описують одну поведінку — початковий стан форми входу.
Не варто об’єднувати в один тест непов’язані сценарії. Наприклад, перевірку початкової форми та відображення помилки після невдалого запиту краще розділити.
// Працює, але менш точно
screen.getByText('Відправити');Краще:
screen.getByRole('button', { name: 'Відправити' });Так тест перевіряє, що це саме кнопка, а не довільний елемент із таким текстом.
getBy... для перевірки відсутності// Викличе помилку, якщо повідомлення відсутнє
expect(screen.getByText('Успішно')).not.toBeInTheDocument();Правильно:
expect(screen.queryByText('Успішно')).not.toBeInTheDocument();await для findBy...// Неправильно
const message = screen.findByText('Дані завантажено');
expect(message).toBeInTheDocument();Правильно:
const message = await screen.findByText('Дані завантажено');
expect(message).toBeInTheDocument();data-testidscreen.getByTestId('submit-button');Якщо кнопку можна знайти за роллю, краще написати:
screen.getByRole('button', { name: 'Надіслати' });// Такий тест залежить від деталей стилізації
expect(button).toHaveClass('button--active');Якщо для користувача важливо, що кнопку можна натиснути, перевіряйте її стан:
expect(button).toBeEnabled();Тест може бути крихким, якщо в ньому дублюється зайва структура тексту. Для назв і кнопок краще чітко вказувати роль та доступну назву:
screen.getByRole('button', { name: /зберегти/i });Елемент повинен бути в DOM одразу — getBy....
Елемент повинен бути відсутнім — queryBy....
Елемент з’являється після асинхронної операції — findBy....
Потрібно отримати кілька елементів — варіанти ...AllBy.
Потрібно перевірити результат — expect із відповідним matcher.
Для інтерактивних елементів спочатку пробуйте getByRole.
Для полів форм використовуйте getByLabelText.
getByTestId залишайте для випадків, коли доступні Query не підходять.
Queries допомагають знайти елементи в DOM, а Assertions — перевірити їхній стан або вміст.
Найважливіші правила:
шукайте елементи за способом взаємодії користувача з ними;
надавайте перевагу getByRole;
використовуйте queryBy... для перевірки відсутності;
використовуйте findBy... для асинхронної появи елементів;
перевіряйте видимий результат, а не внутрішній стан React-компонента;
не зловживайте data-testid і перевірками CSS-класів.