Как проверять конкретный action в reducer в JavaScript/TypeScript?
Что проверяют
Понимаете ли вы, что reducer это чистая функция, и поэтому тестировать его проще всего без стора и без React. Следом спрашивают про иммутабельность и про то, как проверить обработку pending и fulfilled у асинхронного thunk.
Как отвечать
Тест сводится к трём шагам: взять состояние, вызвать reducer с одним action, сравнить результат с ожидаемым.
const prev = { count: 2 };
const next = counterSlice.reducer(prev, increment(3));
expect(next).toEqual({ count: 5 });
expect(prev).toEqual({ count: 2 });Что стоит проговорить:
- action creators вместо литералов: в Redux Toolkit берите
counterSlice.actions.increment(3), а не{ type: 'INCREMENT' }, тогда опечатку в типе поймает TypeScript; - начальное состояние:
reducer(undefined, { type: 'unknown' })возвращаетinitialState; - чужой action: возвращается тот же объект, проверка через
toBe(prev). От этого зависит, перерисуются ли подписанные компоненты; - иммутабельность: исходное состояние не изменилось. В RTK за это отвечает Immer, в самописном редьюсере заморозьте
prevчерезObject.freeze, и мутация броситTypeError; - асинхронные действия:
extraReducersпроверяют, создав action вручную:fetchUser.fulfilled(user, 'req-id', userId), без сети; toStrictEqual: в отличие отtoEqualучитывает поля со значениемundefined, полезно, если reducer не должен оставлять лишних ключей.
Jest и Vitest тут взаимозаменяемы. Стоит упомянуть, что руководство Redux советует основную логику проверять интеграционно, через реальный стор и компонент в React Testing Library, а изолированные тесты оставлять для сложных редьюсеров.
Где спотыкаются
Сравнивают новые объекты через toBe: expect(next).toBe({ count: 5 }) падает всегда, это разные ссылки.
Проверяют одно поле и не видят, что reducer затёр соседние: expect(next.count) пройдёт, даже если остальное состояние потерялось.
Мутируют state в самописном редьюсере и возвращают его же. Проверка результата проходит, а компоненты не перерисовываются, потому что ссылка не изменилась. Без заморозки или проверки prev такой баг тесты не ловят.