Выбор элементов из select и option

В тестировании компонентов с выпадающими списками (<select> и <option>) важно правильно симулировать пользовательские действия и проверять изменения состояния. React Testing Library предоставляет для этого удобные инструменты, позволяя тестировать поведение компонентов так, как это сделал бы реальный пользователь.


Основы работы с <select> и <option>

Элемент <select> представляет собой контейнер для одного или нескольких <option>. Пользователь может выбрать одно значение (или несколько, если используется multiple). В тестах основная задача — корректно имитировать выбор пользователя и убедиться, что компонент реагирует ожидаемым образом.

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

function FruitSelector({ onChange }) {
  return (
    <SELECT data-testid="fruit-select" onCha nge={onChange}>
      <option value="apple">Apple</option>
      <option value="banana">Banana</option>
      <option value="orange">Orange</option>
    </select>
  );
}

Выбор значения через fireEvent

Метод fireEvent позволяет вручную эмулировать события. Для <select> ключевое событие — change.

import { render, screen, fireEvent } FROM '@testing-library/react';
import FruitSelector from './FruitSelector';

test('выбор значения в SELECT обновляет значение', () => {
  const handleChange = jest.fn();
  render(<FruitSelector onCha nge={handleChange} />);

  const SELECT = screen.getByTestId('fruit-select');

  fireEvent.change(select, { target: { value: 'banana' } });

  expect(handleChange).toHaveBeenCalledTimes(1);
  expect(handleChange).toHaveBeenCalledWith(expect.any(Object));
  expect(SELECT.value).toBe('banana');
});

Ключевые моменты:

  • fireEvent.change эмулирует изменение значения <select>.
  • target.value должен соответствовать значению выбранного <option>.
  • После вызова события можно проверять как вызовы колбэков (onChange), так и состояние самого элемента (select.value).

Использование userEvent для имитации пользовательских действий

userEvent обеспечивает более реалистичную имитацию поведения пользователя по сравнению с fireEvent. Он учитывает последовательность действий: фокус, выбор, снятие фокуса.

import { render, screen } FROM '@testing-library/react';
import userEvent FROM '@testing-library/user-event';
import FruitSelector FROM './FruitSelector';

test('выбор option через userEvent', async () => {
  const user = userEvent.setup();
  const handleChange = jest.fn();
  render(<FruitSelector onCha nge={handleChange} />);

  const SELECT = screen.getByTestId('fruit-select');

  await user.selectOptions(select, 'orange');

  expect(handleChange).toHaveBeenCalledTimes(1);
  expect(select.value).toBe('orange');
});

Преимущества userEvent:

  • Более точное воспроизведение действий пользователя (например, поддержка асинхронных операций).
  • Автоматическая генерация событий focus, input, change.
  • Удобно для комплексных компонентов, где реакция зависит от реального пользовательского поведения.

Выбор нескольких значений (multiple select)

Для <select multiple> можно выбирать сразу несколько <option>. В React Testing Library это делается аналогично, с передачей массива значений.

Пример компонента:

function MultiFruitSelector({ onChange }) {
  return (
    <select data-testid="multi-select" multiple onCha nge={onChange}>
      <option value="apple">Apple</option>
      <option value="banana">Banana</option>
      <option value="orange">Orange</option>
    </select>
  );
}

Тестирование выбора нескольких элементов:

test('выбор нескольких значений', async () => {
  const user = userEvent.setup();
  const handleChange = jest.fn();
  render(<MultiFruitSelector onCha nge={handleChange} />);

  const select = screen.getByTestId('multi-select');

  await user.selectOptions(select, ['apple', 'orange']);

  expect(handleChange).toHaveBeenCalled();
  const selectedOptions = Array.FROM(select.selectedOptions).map(o => o.value);
  expect(selectedOptions).toEqual(['apple', 'orange']);
});

Особенности:

  • selectOptions поддерживает массив значений.
  • Проверка выбора проводится через selectedOptions.
  • Можно комбинировать с проверкой колбэков, чтобы убедиться, что компонент корректно обрабатывает множественный выбор.

Выбор элементов по тексту или роли

React Testing Library предоставляет удобные методы поиска элементов:

const select = screen.getByRole('combobox'); // стандартная роль для <select>
const option = screen.getByText('Banana'); // поиск по тексту

Рекомендации:

  • Использовать getByRole предпочтительнее, так как роли соответствуют семантике и делают тесты более устойчивыми.
  • getByText полезен, когда значение опции не совпадает с текстом, отображаемым пользователю.

Проверка состояния компонента после выбора

После эмуляции выбора необходимо убедиться, что компонент корректно обновляет состояние. Для <select> это может быть:

  • Проверка value у самого элемента.
  • Проверка selected у опций.
  • Проверка вызовов onChange и аргументов, переданных в колбэк.

Пример проверки выбора через роли:

test('проверка выбранной опции через роль', async () => {
  const user = userEvent.setup();
  const handleChange = jest.fn();
  render(<FruitSelector onCha nge={handleChange} />);

  const select = screen.getByRole('combobox');
  await user.selectOptions(select, screen.getByRole('option', { name: 'Banana' }));

  expect(select.value).toBe('banana');
});

Советы по устойчивости тестов

  • Использовать data-testid только при необходимости, отдавая предпочтение семантическим селекторам (getByRole, getByLabelText).
  • Предпочитать userEvent для имитации действий пользователя, чтобы тесты отражали реальные сценарии.
  • Проверять не только значение <select>, но и вызовы колбэков и изменения состояния родительских компонентов.
  • Для множественного выбора всегда проверять массив selectedOptions, а не отдельные option.selected.

Эта методология обеспечивает надежное тестирование любых сценариев взаимодействия с <select> и <option> в React, включая одиночный и множественный выбор, динамические изменения списка и проверку вызовов событий.