Testing Library: подход

Testing Library — семейство библиотек для тестирования UI-компонентов через DOM-взаимодействие, основанных на принципе «тестируй поведение, а не реализацию».

Зачем нужно

Тест, проверяющий component.state.isLoading === false, сломается при любом рефакторинге состояния. Тест, проверяющий что «спиннер исчез и данные видны» — не сломается. Testing Library направляет к написанию тестов, устойчивых к изменениям реализации.

Где используется

  • React Testing Library — React-компоненты
  • Vue Testing Library — Vue компоненты
  • Angular Testing Library — Angular
  • Svelte Testing Library, Solid Testing Library
  • DOM Testing Library — ванильный DOM

Основной контент

Ключевой принцип

Guiding principle Testing Library: «Чем больше тесты напоминают то, как реальные пользователи используют ваше ПО, тем больше уверенности они дают».

Приоритет запросов (queries priority)

Testing Library определяет иерархию запросов от наиболее к наименее предпочтительным:

1. Accessible queries (видят screen readers и пользователи)
   - getByRole
   - getByLabelText
   - getByPlaceholderText
   - getByText
   - getByDisplayValue

2. Semantic queries
   - getByAltText
   - getByTitle

3. Test IDs (последний выбор)
   - getByTestId

Варианты запросов: get, query, find

// getBy* — синхронный, выбрасывает если не найдено
screen.getByRole('button');

// queryBy* — синхронный, возвращает null если не найдено (для проверки отсутствия)
expect(screen.queryByRole('alert')).not.toBeInTheDocument;

// findBy* — асинхронный (waitFor внутри), ждёт появления элемента
await screen.findByText('Данные загружены');

AllBy варианты

// Если элементов несколько
const buttons = screen.getAllByRole('button');
expect(buttons).toHaveLength(3);

// Проверка что ни одного нет
expect(screen.queryAllByRole('alert')).toHaveLength(0);

userEvent vs fireEvent

import userEvent from '@testing-library/user-event';

// userEvent — реалистичная симуляция (рекомендуется)
const user = userEvent.setup;
await user.click(button);
await user.type(input, 'Hello world');
await user.keyboard('{Enter}');
await user.selectOptions(select, 'option-value');

// fireEvent — низкоуровневый dispatch события (используй только если userEvent не подходит)
import { fireEvent } from '@testing-library/react';
fireEvent.change(input, { target: { value: 'test' } });

waitFor для асинхронных обновлений

import { waitFor } from '@testing-library/react';

await waitFor(() => {
  expect(screen.getByText('Загрузка завершена')).toBeInTheDocument;
});

// Или через findBy*
await screen.findByText('Загрузка завершена');

Что НЕ нужно тестировать

  • Внутренние детали: state, props, refs
  • Методы компонента напрямую
  • Структура DOM (точные CSS-классы, теги)

Частые ошибки

  • getByText для динамического текста — тест упадёт если изменится перевод; используй getByRole с name
  • Тест реализации через .instance — Testing Library не предоставляет доступ к внутренностям намеренно
  • act warnings — появляются при обновлении state без обёртки; решается через await userEvent.* или await waitFor(...)

Связанные темы

Ресурсы