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的特性和你的测试方式不匹配:
redux-mock-store不执行reducer逻辑
这个库只是模拟了store的dispatch和getState方法,只会记录你dispatch过的action,不会真正调用reducer去更新状态。所以你dispatchgetUserDataReducer.pending后,store的初始状态根本没变化,自然拿不到更新后的userIsLoaded和userIsLoading。正确的测试路径
要测试异步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); }); });
- 第一个测试正常的原因
第一个测试直接调用了userSlice.reducer,本质是在测试纯函数的输入输出,完全符合Redux reducer的测试规范,所以能正确得到更新后的状态。
内容的提问来源于stack exchange,提问作者Ren
相关产品推荐
相关产品推荐

