Тестирование хуков
Тестирование React-хуков выполняется через
renderHookиз@testing-library/react, который создаёт минимальный компонент-обёртку для вызова хука и обеспечивает доступ к его состоянию и методам.
Зачем нужно
Кастомные хуки содержат логику, которую сложно тестировать через компоненты (множество сценариев, граничные случаи). renderHook позволяет изолированно тестировать хук без UI, проверять обновления состояния и side effects напрямую.
Где используется
- Кастомные хуки с бизнес-логикой (
useCart,useAuth,useFetch) - Хуки с асинхронными операциями
- Хуки, использующие Context
- Хуки с debounce, throttle, таймерами
Основной контент
Базовый тест хука
// useCounter.ts
import { useState } from 'react';
export function useCounter(initial = 0) {
const [count, setCount] = useState(initial);
const increment = () => setCount((c) => c + 1);
const decrement = () => setCount((c) => c - 1);
const reset = () => setCount(initial);
return { count, increment, decrement, reset };
}
// useCounter.test.ts
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
test('начинает с начального значения', () => {
const { result } = renderHook( => useCounter(5));
expect(result.current.count).toBe(5);
});
test('increment увеличивает count', () => {
const { result } = renderHook( => useCounter);
act(() => {
result.current.increment;
});
expect(result.current.count).toBe(1);
});
test('reset возвращает к начальному значению', () => {
const { result } = renderHook( => useCounter(10));
act(() => {
result.current.increment;
result.current.increment;
result.current.reset();
});
expect(result.current.count).toBe(10);
});
Тестирование асинхронного хука
// useFetch.ts
export function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData)
.catch((e) => setError(e.message))
.finally( => setLoading(false));
}, [url]);
return { data, loading, error };
}
// useFetch.test.ts
global.fetch = jest.fn;
test('загружает данные', async () => {
(fetch as jest.Mock).mockResolvedValue({
json: async => ({ id: 1, name: 'Alice' }),
});
const { result } = renderHook( => useFetch('/api/users/1'));
expect(result.current.loading).toBe(true);
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.data).toEqual({ id: 1, name: 'Alice' });
expect(result.current.error).toBeNull();
});
test('обрабатывает ошибку', async () => {
(fetch as jest.Mock).mockRejectedValue(new Error('Network Error'));
const { result } = renderHook( => useFetch('/api/users/1'));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.error).toBe('Network Error');
expect(result.current.data).toBeNull();
});
Хук с Context — wrapper
// useTheme.ts использует ThemeContext
test('useTheme возвращает текущую тему', () => {
const wrapper = ({ children }) => (
<ThemeProvider value="dark">{children}</ThemeProvider>
);
const { result } = renderHook( => useTheme, { wrapper });
expect(result.current.theme).toBe('dark');
});
Хук с параметрами (rerender)
test('обновляет данные при смене url', async () => {
(fetch as jest.Mock).mockResolvedValue({ json: async => ({ id: 1 }) });
const { result, rerender } = renderHook(
({ url }) => useFetch(url),
{ initialProps: { url: '/api/users/1' } }
);
await waitFor( => expect(result.current.loading).toBe(false));
expect(fetch).toHaveBeenCalledWith('/api/users/1');
(fetch as jest.Mock).mockResolvedValue({ json: async => ({ id: 2 }) });
rerender({ url: '/api/users/2' });
await waitFor( => expect(result.current.loading).toBe(false));
expect(fetch).toHaveBeenCalledWith('/api/users/2');
});
Частые ошибки
- Нет
actпри обновлении состояния — Jest предупредит о "act warning"; оборачивай любые вызовы, меняющие state - Нет
waitForдля асинхронных обновлений —result.current.loadingпроверяется до завершения Promise; используйwaitFor - Тест реализации вместо поведения — не проверяй
useStateнапрямую; проверяй что возвращает хук
Связанные темы
- _MOC Тестирование
- React Testing Library -- основы
- Тестирование рендеринга
- Jest -- тестирование асинхронного кода