Хрупкие селекторы в queries

React Testing Library ориентирована на использование селекторов, которые имитируют поведение пользователя при взаимодействии с компонентами. Важно понимать, что некоторые селекторы могут быть уязвимыми к изменениям в структуре DOM. Это приводит к хрупким тестам, которые ломаются при незначительных изменениях в интерфейсе. Рассмотрим, как выбрать правильные селекторы для тестов и избежать таких проблем.

Селекторы и их роль в тестировании

В React Testing Library селекторы позволяют находить элементы на странице для последующего взаимодействия с ними в тестах. Основные категории селекторов включают:

  • По тексту: getByText, queryByText
  • По роли: getByRole, queryByRole
  • По меткам: getByLabelText, queryByLabelText
  • По имени: getByDisplayValue, queryByDisplayValue
  • По размещению: getByTestId, queryByTestId

При выборе селектора важно понимать, что он должен отражать реальное поведение пользователя, а не зависеть от внутренней структуры компонента. Например, поиск элементов по data-testid часто приводит к хрупким тестам, поскольку идентификаторы могут изменяться без изменения функциональности компонента.

Почему селекторы становятся хрупкими?

Некоторые селекторы оказываются уязвимыми при изменении внутренней реализации компонента. Это связано с тем, что тесты ориентируются на технические детали, а не на то, как компонент воспринимается пользователем.

Пример хрупкого теста:

test('отображение кнопки с правильным текстом', () => {
  const { getByTestId } = render(<Button data-testid="submit-button">Отправить</Button>);
  const button = getByTestId('submit-button');
  expect(button).toHaveTextContent('Отправить');
});

В этом примере тест зависит от data-testid, что делает его уязвимым к изменениям в реализации компонента, например, при изменении атрибута data-testid.

Как избежать хрупких селекторов?

Использование ролей

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

test('кнопка должна отображать правильный текст', () => {
  const { getByRole } = render(<Button>Отправить</Button>);
  const button = getByRole('button');
  expect(button).toHaveTextContent('Отправить');
});

Здесь тест ориентируется на роль элемента (button), что делает его менее зависимым от внутренней реализации.

Использование меток

Селекторы по меткам (getByLabelText, getByPlaceholderText) идеально подходят для взаимодействия с формами. Они помогают находить элементы, основываясь на метках, которые видит пользователь. Это обеспечивает более устойчивые тесты, так как метки обычно не меняются без изменения бизнес-логики.

test('пользователь может ввести имя', () => {
  const { getByLabelText } = render(<label htmlFor="name">Имя<input id="name" /></label>);
  const input = getByLabelText(/имя/i);
  fireEvent.change(input, { target: { value: 'Иван' } });
  expect(input.value).toBe('Иван');
});

В данном случае тест использует метку Имя, что делает его менее зависимым от внутренних идентификаторов.

Тестирование доступности

Роли и метки также важны для обеспечения доступности, так как они помогают вспомогательным технологиям (например, экранным читалкам) правильно интерпретировать структуру интерфейса. Это способствует не только стабильности тестов, но и улучшению пользовательского опыта для людей с ограниченными возможностями.

Не рекомендуется: использование data-testid

Селекторы, использующие атрибуты типа data-testid, могут быть полезны в некоторых случаях, но их следует использовать с осторожностью. Этот метод привязывает тесты к внутренним деталям компонента, что может привести к проблемам при изменении реализации.

Использование data-testid может быть оправдано только в следующих случаях:

  • Когда невозможно использовать другие более стабильные селекторы.
  • Когда тестируется сложная логика, не привязанная к рендеру элементов для пользователя.

Пример использования data-testid:

test('проверка отрисовки кнопки', () => {
  const { getByTestId } = render(<Button data-testid="submit-button" />);
  const button = getByTestId('submit-button');
  expect(button).toBeInTheDocument();
});

Этот тест ломается, если в компоненте изменится атрибут data-testid.

Как тестировать компоненты, не привязываясь к структуре?

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

  • Тексту на кнопках или ссылках.
  • Ролям элементов.
  • Формам и меткам.

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

Тесты на доступность

Для улучшения тестов можно добавить проверки доступности (A11Y), проверяя, доступны ли элементы для пользователей с ограниченными возможностями. Это важно не только с точки зрения тестирования, но и для повышения качества приложения в целом.

React Testing Library имеет встроенные инструменты для тестирования доступности, такие как axe-core, который можно интегрировать с тестами для проверки нарушений стандартов доступности.

Пример использования axe-core:

import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

test('компонент соответствует стандартам доступности', async () => {
  const { container } = render(<Button>Отправить</Button>);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

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

Заключение

Хрупкие селекторы в React Testing Library могут быть причиной ненадёжных тестов, которые ломаются при малейших изменениях в реализации. Чтобы избежать этого, следует выбирать селекторы, ориентирующиеся на поведение пользователя, такие как роли, метки и текстовые содержимые. Эти подходы делают тесты более устойчивыми и легко поддерживаемыми, обеспечивая более высокое качество кода.