Библиотека @testing-library/react-hooks

Для работы с @testing-library/react-hooks необходимо установить пакет через npm или yarn:

npm install @testing-library/react-hooks --save-dev

или

yarn add @testing-library/react-hooks --dev

Библиотека интегрируется с Jest по умолчанию, однако можно использовать и другие тестовые раннеры. Для корректной работы следует убедиться, что версия react совместима с версией @testing-library/react-hooks.

Основные функции и API

renderHook

Функция renderHook используется для запуска и тестирования кастомных React-хуков. Она принимает колбэк, возвращающий хук, и опциональный объект с параметрами:

import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';

const { result } = renderHook(() => useCounter());
  • result — объект, содержащий текущее состояние хука и функции для обновления.
  • rerender — позволяет повторно вызвать хук с новыми аргументами.
  • unmount — размонтирует хук, полезно для проверки очистки эффектов.
  • waitFor и waitForNextUpdate — используются для асинхронных операций внутри хука.

act

Функция act гарантирует, что все изменения состояния и эффекты хука будут завершены до выполнения ассертов. Применяется как синхронно, так и с асинхронными функциями:

act(() => {
  result.current.increment();
});
expect(result.current.count).toBe(1);

Для асинхронных обновлений:

await act(async () => {
  await result.current.fetchData();
});
expect(result.current.data).toEqual(expectedData);

Тестирование состояния

Хуки часто управляют состоянием, поэтому проверка правильного обновления состояния является ключевой задачей.

const { result } = renderHook(() => useCounter());

expect(result.current.count).toBe(0);

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

expect(result.current.count).toBe(1);

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

  • result.current всегда содержит актуальное значение, даже после нескольких вызовов act.
  • Сравнение значений должно учитывать ссылочную целостность для объектов и массивов.

Тестирование эффектов

Для хуков с побочными эффектами (useEffect) важно проверять их поведение при монтировании и размонтировании:

const effectMock = jest.fn();

function useTestEffect() {
  useEffect(() => {
    effectMock();
    return () => effectMock();
  }, []);
}

const { unmount } = renderHook(() => useTestEffect());

expect(effectMock).toHaveBeenCalledTimes(1);
unmount();
expect(effectMock).toHaveBeenCalledTimes(2);

Асинхронные эффекты

Если эффект выполняет асинхронную операцию, используют waitForNextUpdate:

const { result, waitForNextUpdate } = renderHook(() => useAsyncData());

await waitForNextUpdate();

expect(result.current.loading).toBe(false);
expect(result.current.data).toEqual(expectedData);

waitForNextUpdate ожидает один цикл обновления состояния. Для множественных обновлений используется цикл или waitFor.

Параметры и повторный рендер

renderHook позволяет передавать параметры в хук и изменять их без пересоздания:

const { result, rerender } = renderHook(({ initial }) => useCounter(initial), {
  initialProps: { initial: 0 },
});

expect(result.current.count).toBe(0);

rerender({ initial: 5 });
expect(result.current.count).toBe(5);

Такой подход полезен для тестирования реактивного поведения хуков при изменении входных данных.

Тестирование пользовательских хуков с контекстом

Если хук использует React.Context, требуется обернуть его в провайдер через опцию wrapper:

const wrapper = ({ children }) => <MyContext.Provider value={value}>{children}</MyContext.Provider>;

const { result } = renderHook(() => useMyContext(), { wrapper });

expect(result.current).toBe(value);

wrapper может быть любым компонентом, который обеспечивает нужный контекст или провайдеры.

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

  • Всегда использовать act при изменении состояния или вызове функций хука.
  • Для асинхронных эффектов использовать waitFor или waitForNextUpdate, чтобы избежать предупреждений React.
  • Проверять корректность очистки эффектов при размонтировании с помощью unmount.
  • При тестировании объектов и массивов сравнивать содержимое, а не ссылки, используя методы toEqual или toMatchObject.
  • Использовать rerender для проверки реактивного поведения хуков при изменении входных данных.
  • При сложных хуках с контекстом и провайдерами создавать обертки через wrapper для изоляции тестов.

Интеграция с Jest и другими библиотеками

@testing-library/react-hooks совместим с Jest, Mocha и другими тестовыми фреймворками. В случае Jest можно использовать мок-функции через jest.fn() для проверки вызовов и состояния побочных эффектов. Для TypeScript важно правильно типизировать result.current для корректного автодополнения и проверки типов.


Хорошо структурированное использование @testing-library/react-hooks позволяет тестировать хуки так же надежно, как компоненты, обеспечивая контроль состояния, побочных эффектов и реактивного поведения в изоляции.