使用Jest和RTL测试时Mock React-Redux 8的Reducer函数问题
解决React-Redux 8迁移后Mock Action调用的问题
方案1:Mock自定义useAppDispatch钩子捕获派发的Action
如果你的项目封装了useAppDispatch(大部分Redux Toolkit项目会这么做),直接Mock这个钩子就能捕获组件中派发的所有Action,验证调用情况:
步骤:
- 假设你的自定义钩子定义在
src/app/hooks.ts:
// src/app/hooks.ts import { useDispatch, useSelector } from 'react-redux' import type { TypedUseSelectorHook } from 'react-redux' import type { RootState, AppDispatch } from './store' export const useAppDispatch: () => AppDispatch = useDispatch export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
- 在测试文件中Mock该钩子,并创建一个mock dispatch函数:
import { useAppDispatch } from '../app/hooks'; // 创建mock dispatch函数 const mockDispatch = jest.fn(); // Mock自定义钩子,返回mock dispatch jest.mock('../app/hooks', () => ({ ...jest.requireActual('../app/hooks'), useAppDispatch: () => mockDispatch, })); describe('AccountActivation', () => { // 每次测试前清空mock调用记录 beforeEach(() => { mockDispatch.mockClear(); }); it('进入页面后调用激活接口', () => { renderWithProviders( <Router> <EmployeeAccountActivation /> </Router>, { preloadedState: { account: { isAuthenticated: false, loading: false, }, }, } ); // 验证dispatch被调用的次数 expect(mockDispatch).toHaveBeenCalledTimes(1); // 验证派发的Action是否符合预期(替换为你的实际Action类型和payload) expect(mockDispatch).toHaveBeenCalledWith(expect.objectContaining({ type: 'account/activate', // 如果有payload,可添加验证:payload: expect.any(String) })); }); });
这个方案和你之前Mock单个props的逻辑最接近,改动极小,适合批量迁移测试。
方案2:Mock单个Action Creator函数
如果你的Action是单独导出的(比如用Redux Toolkit的createAsyncThunk或普通Action Creator),直接Mock对应的Action Creator即可追踪调用:
步骤:
- 假设你的激活Action定义在
src/features/account/accountSlice.ts:
// src/features/account/accountSlice.ts import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; export const activate = createAsyncThunk( 'account/activate', async (activationToken: string) => { // 实际API调用逻辑 } ); const accountSlice = createSlice({ name: 'account', initialState: { isAuthenticated: false, loading: false }, reducers: {}, extraReducers: (builder) => { builder.addCase(activate.pending, (state) => { state.loading = true; }); }, }); export default accountSlice.reducer;
- 在测试中Mock该Action Creator:
import { activate } from '../features/account/accountSlice'; // Mock激活Action Creator jest.mock('../features/account/accountSlice', () => ({ ...jest.requireActual('../features/account/accountSlice'), activate: jest.fn(), })); describe('AccountActivation', () => { beforeEach(() => { // 清空mock调用记录 (activate as jest.Mock).mockClear(); }); it('进入页面后调用激活接口', () => { renderWithProviders( <Router> <EmployeeAccountActivation /> </Router>, { preloadedState: { account: { isAuthenticated: false, loading: false, }, }, } ); // 验证Action Creator被调用的次数 expect(activate).toHaveBeenCalledTimes(1); // 如果Action需要参数,验证参数是否正确 // expect(activate).toHaveBeenCalledWith('expected-token'); }); });
这个方案更聚焦单个Action,适合只需要验证Action是否被触发的场景。
关于你遇到的Unexpected key "account"错误
你之前尝试Mock reducer文件时出现这个错误,是因为jest.mock覆盖了原始reducer的导出结构,导致Root Reducer中不再包含account这个key,但你在preloadedState中传入了该字段,Redux无法匹配对应的reducer,因此报错。正确的做法是Mock Action Creator而非Reducer本身,因为Reducer是处理Action的逻辑,而你要验证的是Action是否被派发。
方案3:自定义Store替换单个Reducer逻辑(进阶)
如果需要验证Reducer对Action的处理逻辑,可以创建自定义Store,替换对应Slice的Reducer:
import { configureStore } from '@reduxjs/toolkit'; import rootReducer from '../app/rootReducer'; import { activate } from '../features/account/accountSlice'; describe('AccountActivation', () => { it('进入页面后调用激活接口并更新状态', () => { const originalAccountReducer = rootReducer.account; // 创建mock的Action处理函数 const mockActivateHandler = jest.fn((state, action) => { // 保留原始Reducer逻辑 return originalAccountReducer(state, action); }); // 创建自定义Store,拦截激活相关的Action const customStore = configureStore({ reducer: { ...rootReducer, account: (state, action) => { // 匹配激活Action的所有状态(pending/fulfilled/rejected) if ([activate.pending.type, activate.fulfilled.type, activate.rejected.type].includes(action.type)) { mockActivateHandler(state, action); } return originalAccountReducer(state, action); }, }, preloadedState: { account: { isAuthenticated: false, loading: false, }, }, }); renderWithProviders( <Router> <EmployeeAccountActivation /> </Router>, { store: customStore, } ); // 验证Reducer处理了该Action expect(mockActivateHandler).toHaveBeenCalled(); }); });
这个方案适合需要验证状态更新逻辑的场景,但相对前两个方案复杂度更高。
内容的提问来源于stack exchange,提问作者Sqzi
相关产品推荐
相关产品推荐

