useReducer является мощным инструментом управления
состоянием в React, особенно в сложных компонентах с множественными
взаимосвязанными состояниями. Тестирование такой логики отличается от
стандартного тестирования простых компонентов, поскольку основной упор
делается на проверку корректного изменения состояния при разных
действиях.
Редьюсер в React имеет стандартный вид:
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, count: state.count - 1 };
case 'reset':
return initialState;
default:
return state;
}
}
Ключевые элементы для тестирования:
initialState)action.type)Тестирование должно охватывать каждый кейс, включая обработку неизвестных действий.
Тестирование useReducer логики не всегда требует
рендеринга компонента. Можно тестировать чистую функцию редьюсера
напрямую:
import { reducer, initialState } from './counterReducer';
test('increment action increases count', () => {
const state = { count: 0 };
const newState = reducer(state, { type: 'increment' });
expect(newState.count).toBe(1);
});
test('decrement action decreases count', () => {
const state = { count: 1 };
const newState = reducer(state, { type: 'decrement' });
expect(newState.count).toBe(0);
});
test('reset action returns initial state', () => {
const state = { count: 5 };
const newState = reducer(state, { type: 'reset' });
expect(newState).toEqual(initialState);
});
test('unknown action returns current state', () => {
const state = { count: 3 };
const newState = reducer(state, { type: 'unknown' });
expect(newState).toBe(state);
});
Преимущества такого подхода:
Для проверки поведения компонента, использующего
useReducer, важно тестировать взаимодействие
пользователя с интерфейсом и влияние этих действий на
состояние.
Пример простого счетчика:
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments count on button click', () => {
render(<Counter />);
const incrementButton = screen.getByText('Increment');
fireEvent.click(incrementButton);
const countDisplay = screen.getByText(/count: 1/i);
expect(countDisplay).toBeInTheDocument();
});
test('decrements count on button click', () => {
render(<Counter />);
const decrementButton = screen.getByText('Decrement');
fireEvent.click(decrementButton);
const countDisplay = screen.getByText(/count: -1/i);
expect(countDisplay).toBeInTheDocument();
});
test('resets count to initial state', () => {
render(<Counter />);
const incrementButton = screen.getByText('Increment');
const resetButton = screen.getByText('Reset');
fireEvent.click(incrementButton);
fireEvent.click(incrementButton);
fireEvent.click(resetButton);
const countDisplay = screen.getByText(/count: 0/i);
expect(countDisplay).toBeInTheDocument();
});
Особенности тестирования с RTL:
Для более сложных редьюсеров с множественными действиями и состояниями рекомендуется:
Разделять логику редьюсера на отдельные функции, если есть повторяющийся код.
Создавать тестовые сценарии, покрывающие цепочки действий:
// Пример: последовательность действий
let state = initialState;
state = reducer(state, { type: 'increment' });
state = reducer(state, { type: 'increment' });
state = reducer(state, { type: 'decrement' });
expect(state.count).toBe(1);Использовать проверку неизменяемости состояния:
const state = { count: 0 };
const newState = reducer(state, { type: 'increment' });
expect(newState).not.toBe(state); // новый объект, не мутацияЕсли редьюсер обрабатывает асинхронные действия, например через
dispatch({ type: 'fetchSuccess', payload }), тестирование
может включать:
jest.mock или
msw.waitFor для ожидания обновления DOM:import { render, screen, waitFor } from '@testing-library/react';
import DataComponent from './DataComponent';
import { fetchData } from './api';
jest.mock('./api');
test('updates state after fetch', async () => {
fetchData.mockResolvedValue({ value: 42 });
render(<DataComponent />);
await waitFor(() => {
expect(screen.getByText(/value: 42/i)).toBeInTheDocument();
});
});
action.type для
уменьшения опечаток.Тщательное тестирование useReducer обеспечивает
надежность компонентов, делает код предсказуемым и упрощает поддержку
сложной логики состояния в React.