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

Redux Toolkit单元测试中store.dispatch(action)无效问题排查

问题

在Redux Toolkit单元测试场景中,直接调用userSlice.reducer可正常工作,但使用store.dispatch(action)(如dispatch getUserDataReducer.pending)时Redux状态并未更新。相关代码及报错信息如下:

user.tsx

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { refreshAccessToken } from "../../api/auth";
import { getUserData, patchUserData } from "../../api/user";
import { UserDataResponse } from "../../types/user";
import { RootState } from "../store";
import { logoutUserReducer } from "./auth";
export interface User {
  email?: string;
  name?: string;
  password?: string;
}

export interface InitialStateUser {
  user: User | null;
  userIsLoading: boolean;
  userIsLoaded?: boolean;
}

const initialState: InitialStateUser = {
  user: null,
  userIsLoading: false,
};

export const getUserDataReducer = createAsyncThunk(
  "getUserData",
  async (_, { rejectWithValue }) => {
    // some async code with FETCH API and getUserData()
  }
);

export const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(getUserDataReducer.fulfilled, (state, action) => {
        state.user = action.payload.user;
        state.userIsLoading = false;
        state.userIsLoaded = true;
      })
      .addCase(getUserDataReducer.pending, (state) => {
        state.userIsLoading = true;
        state.userIsLoaded = false;
      })
      .addCase(getUserDataReducer.rejected, (state, action) => {
        state.userIsLoading = false;
        state.userIsLoaded = false;
      })
  },
});

export default userSlice.reducer;

user.test.tsx

import { AppDispatch, RootState } from "../store";
import configureMockStore, { MockStoreEnhanced } from "redux-mock-store";
import thunk from "redux-thunk";
import { initialIngredientsState } from "./ingredients.test";
import { initialOrderState } from "./order.test";
import userReducer, {
  getUserDataReducer,
  InitialStateUser,
  userSlice,
} from "../slices/user";
import fetchMock from "fetch-mock";
import { BASE_URL } from "../../api";
import { getCookie } from "../../utils/cookieHandler";

const initialUserState: InitialStateUser = {
  user: null,
  userIsLoading: false,
};

const mockStoreInitialState = {
  user: { ...initialUserState }
};

describe("user reducer", () => {
  let store: MockStoreEnhanced<RootState>;
  beforeEach(() => {
    store = mockStore(mockStoreInitialState);
  });
  afterEach(() => {
    fetchMock.restore();
  });
  // 正常工作
  it("should getUserData fullfilled", () => {
    const newState = userSlice.reducer(initialUserState, {
      type: "getUserData/fulfilled",
      payload: {
        user: { email: "test@gmail.com", name: "testU2dfd" },
      },
    });

    expect(newState.user).toEqual({
      email: "test@gmail.com",
      name: "testU2dfd",
    });
    expect(newState.userIsLoaded).toBe(true);
  });
  // 无法正常工作
  it("should getUserData pending", async () => {
    await store.dispatch(getUserDataReducer.pending(""));
    const { userIsLoaded, userIsLoading } = store.getState().user;
    console.log(store.getState());
    expect(userIsLoaded).toBe(false);
    expect(userIsLoading).toBe(true);
  });
});

报错信息

● user reducer › should getUserData pending

    expect(received).toBe(expected) // Object.is equality

    Expected: false
    Received: undefined
解决方案

问题核心在于redux-mock-store的特性和你的测试方式不匹配:

  1. redux-mock-store不执行reducer逻辑
    这个库只是模拟了store的dispatch和getState方法,只会记录你dispatch过的action,不会真正调用reducer去更新状态。所以你dispatchgetUserDataReducer.pending后,store的初始状态根本没变化,自然拿不到更新后的userIsLoaded和userIsLoading。

  2. 正确的测试路径
    要测试异步thunk对应的状态变化,有两种可行方式:

  • 直接测试reducer(推荐):像第一个测试用例那样,直接调用reducer传入初始状态和对应action,这是测试Redux reducer纯函数逻辑的标准方式,示例如下:
    it("should handle getUserData pending", () => {
      const newState = userSlice.reducer(initialUserState, getUserDataReducer.pending(""));
      expect(newState.userIsLoaded).toBe(false);
      expect(newState.userIsLoading).toBe(true);
    });
    
  • 使用真实测试store:用Redux Toolkit官方的configureStore创建真实的测试store,这样dispatch action后会真正执行reducer更新状态,示例如下:
    import { configureStore } from "@reduxjs/toolkit";
    // ...其他导入
    
    describe("user reducer with real store", () => {
      let store;
      beforeEach(() => {
        store = configureStore({
          reducer: {
            user: userReducer,
            // 加入其他需要的reducer
          },
          preloadedState: mockStoreInitialState
        });
      });
    
      it("should update state on getUserData pending", async () => {
        // 手动dispatch对应action类型
        await store.dispatch({ type: getUserDataReducer.pending.type });
        const { userIsLoaded, userIsLoading } = store.getState().user;
        expect(userIsLoaded).toBe(false);
        expect(userIsLoading).toBe(true);
      });
    });
    
  1. 第一个测试正常的原因
    第一个测试直接调用了userSlice.reducer,本质是在测试纯函数的输入输出,完全符合Redux reducer的测试规范,所以能正确得到更新后的状态。

内容的提问来源于stack exchange,提问作者Ren

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 19:27:22