You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Jest和RTL测试时Mock React-Redux 8的Reducer函数问题

解决React-Redux 8迁移后Mock Action调用的问题

方案1:Mock自定义useAppDispatch钩子捕获派发的Action

如果你的项目封装了useAppDispatch(大部分Redux Toolkit项目会这么做),直接Mock这个钩子就能捕获组件中派发的所有Action,验证调用情况:

步骤:

  1. 假设你的自定义钩子定义在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
  1. 在测试文件中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即可追踪调用:

步骤:

  1. 假设你的激活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;
  1. 在测试中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 14:10:35