Тестирование хуков является критически важным аспектом разработки современных приложений на Preact. Хуки предоставляют мощные возможности для управления состоянием, побочными эффектами и контекстом компонентов, и корректное тестирование позволяет убедиться в их правильной работе без необходимости запускать полный интерфейс.
В Preact используются те же концепции, что и в React, поэтому для тестирования хуков применяются схожие стратегии:
Юнит-тесты отдельных функций-хуков Когда хук реализует чистую логику (например, управление состоянием или вычисление значений), его можно тестировать как обычную функцию. Это позволяет изолировать логику от рендеринга компонентов.
Тестирование через вспомогательный компонент Для тестов, где важно наблюдать взаимодействие хуков с жизненным циклом компонента, создается минимальный компонент-обертка, который использует тестируемый хук. Это позволяет:
useEffect).Для Preact подходят следующие библиотеки:
Пример установки необходимых пакетов:
npm install --save-dev jest @testing-library/preact
useStateuseState является базовым хуком для управления
состоянием. Тестирование его логики обычно включает:
Пример теста:
import { renderHook, act } from '@testing-library/preact-hooks';
import { useState } from 'preact/hooks';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(c => c + 1);
return { count, increment };
}
test('useCounter увеличивает счетчик', () => {
const { result } = renderHook(() => useCounter(5));
expect(result.current.count).toBe(5);
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(6);
});
Ключевые моменты:
act для оборачивания действий, изменяющих
состояние.useEffectuseEffect управляет побочными эффектами компонентов. Для
тестирования важно учитывать порядок вызовов и очистку эффектов.
Пример тестирования таймера:
import { renderHook, act } from '@testing-library/preact-hooks';
import { useEffect, useState } from 'preact/hooks';
function useTimer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(interval);
}, []);
return seconds;
}
jest.useFakeTimers();
test('useTimer увеличивает секунды с интервалом', () => {
const { result } = renderHook(() => useTimer());
expect(result.current).toBe(0);
act(() => {
jest.advanceTimersByTime(1000);
});
expect(result.current).toBe(1);
act(() => {
jest.advanceTimersByTime(2000);
});
expect(result.current).toBe(3);
});
Особенности:
jest.useFakeTimers() для контроля
времени.cleanup через очистку таймеров после
завершения теста.useContextДля хуков, взаимодействующих с контекстом, создается провайдер в тесте:
import { createContext } from 'preact';
import { renderHook } from '@testing-library/preact-hooks';
import { useContext } from 'preact/hooks';
const ThemeContext = createContext('light');
function useTheme() {
return useContext(ThemeContext);
}
test('useTheme получает значение контекста', () => {
const wrapper = ({ children }) => (
<ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
);
const { result } = renderHook(() => useTheme(), { wrapper });
expect(result.current).toBe('dark');
});
Заметки:
wrapper позволяет передать нужное
значение контекста.При работе с хуками, которые используют внешние сервисы или API, важно изолировать тест:
fetch, axios)
через Jest.localStorage)
тестовыми версиями.jest.fn() для проверки вызовов функций
обратного вызова.Пример мокинга fetch:
global.fetch = jest.fn(() =>
Promise.resolve({ json: () => Promise.resolve({ data: 42 }) })
);
test('useFetchData возвращает данные', async () => {
const { result, waitForNextUpdate } = renderHook(() => useFetchData('/api/data'));
await waitForNextUpdate();
expect(result.current.data).toEqual({ data: 42 });
expect(fetch).toHaveBeenCalledWith('/api/data');
});
Для хуков, зависящих от аргументов или пропсов, тестирование
ререндеров позволяет убедиться в правильной работе мемоизации и
useEffect:
const { result, rerender } = renderHook(({ value }) => useCustomHook(value), {
initialProps: { value: 10 }
});
expect(result.current).toBe(20);
rerender({ value: 15 });
expect(result.current).toBe(30);
rerender позволяет симулировать изменение входных
данных без пересоздания хука с нуля.useState, вычисления значений).useEffect и асинхронных операций.Эти подходы позволяют создавать надежные и поддерживаемые приложения на Preact с предсказуемым поведением хуков.