Юнит-тестирование хуков

Юнит-тестирование хуков в React Aria — это ключевой элемент обеспечения стабильности и доступности компонентов, особенно учитывая, что библиотека ориентирована на управление поведением и доступностью пользовательского интерфейса, а не на визуальное отображение. Основная цель — проверка того, что хук корректно предоставляет необходимые пропсы, состояния и обработчики событий, соответствующие стандартам доступности.


Основы тестирования хуков

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

  1. @testing-library/react-hooks — позволяет рендерить и тестировать хуки независимо от компонентов.
  2. renderHook — основной метод для инициализации и вызова хука в тесте.
  3. act — гарантирует корректное выполнение обновлений состояния и побочных эффектов.

Пример базового теста для кастомного хука:

import { renderHook, act } from '@testing-library/react-hooks';
import { useButton } from '@react-aria/button';

test('useButton возвращает необходимые пропсы для кнопки', () => {
  const { result } = renderHook(() => useButton({ onPress: jest.fn() }));
  
  expect(result.current.buttonProps).toHaveProperty('role', 'button');
  expect(typeof result.current.buttonProps.onClick).toBe('function');
});

Тестирование состояния и взаимодействия

Многие хуки в React Aria управляют интерактивным состоянием, например useToggleState или useCheckbox. Тестирование таких хуков требует проверки как начального состояния, так и реакции на события.

Пример проверки useToggleState:

import { renderHook, act } from '@testing-library/react-hooks';
import { useToggleState } from '@react-stately/toggle';

test('useToggleState корректно меняет состояние', () => {
  const { result } = renderHook(() => useToggleState({ defaultSelected: false }));

  expect(result.current.isSelected).toBe(false);

  act(() => {
    result.current.toggle();
  });

  expect(result.current.isSelected).toBe(true);
});

В этом примере:

  • result.current.isSelected — текущее состояние выбора.
  • toggle() — метод изменения состояния.

Важно использовать act, чтобы все обновления состояний были корректно зафиксированы в тестовой среде.


Проверка пропсов доступности (ARIA attributes)

React Aria предоставляет хелперы для генерации атрибутов доступности, таких как aria-checked, aria-expanded, aria-label. Юнит-тесты должны проверять корректность этих атрибутов в разных состояниях.

Пример теста для useCheckbox:

import { renderHook } from '@testing-library/react-hooks';
import { useCheckbox } from '@react-aria/checkbox';
import { useToggleState } from '@react-stately/toggle';

test('useCheckbox возвращает правильные ARIA атрибуты', () => {
  const state = useToggleState({ defaultSelected: true });
  const { result } = renderHook(() => useCheckbox({ isSelected: state.isSelected }, state));

  expect(result.current.inputProps['aria-checked']).toBe(true);
});

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

  • Передача состояния через useToggleState.
  • Проверка атрибутов aria-checked, aria-disabled и других.

Тестирование событий

Большинство хуков React Aria управляют событиями: клики, клавиши, фокус. Тестирование должно имитировать взаимодействия и проверять вызовы обработчиков.

Пример для useButton:

import { renderHook, act } from '@testing-library/react-hooks';
import { useButton } from '@react-aria/button';

test('useButton корректно вызывает onPress', () => {
  const onPr ess = jest.fn();
  const { result } = renderHook(() => useButton({ onPress }));

  act(() => {
    result.current.buttonProps.onClick();
  });

  expect(onPress).toHaveBeenCalledTimes(1);
});

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

  • Симуляция событий напрямую через пропсы.
  • Проверка количества вызовов и контекста.

Мокинг контекста и провайдеров

Некоторые хуки React Aria требуют контекста, например useMenuItem зависит от useMenuState. В юнит-тестах используется мокинг провайдеров или создание минимального состояния для тестирования:

import { renderHook, act } from '@testing-library/react-hooks';
import { useMenuItem } from '@react-aria/menu';
import { useMenuState } from '@react-stately/menu';

test('useMenuItem корректно обновляет состояние меню', () => {
  const state = useMenuState({ items: ['Item 1', 'Item 2'] });
  const { result } = renderHook(() => useMenuItem({ key: 'Item 1' }, state));

  expect(result.current.menuItemProps['aria-selected']).toBe(false);

  act(() => {
    state.select('Item 1');
  });

  expect(result.current.menuItemProps['aria-selected']).toBe(true);
});

Практические рекомендации

  • Использовать renderHook для независимого тестирования хуков.
  • Оборачивать все изменения состояния в act.
  • Проверять ARIA атрибуты для всех интерактивных элементов.
  • Симулировать пользовательские события напрямую через возвращаемые пропсы.
  • Мокать контексты и состояния, необходимые для работы хука.
  • Покрывать тестами разные сценарии: начальное состояние, изменение состояния, взаимодействие пользователя.

Эти подходы позволяют создавать надежные тесты, которые обеспечивают корректное поведение и доступность интерфейсов при использовании React Aria.