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

Jest测试中无需Thunk解决‘Actions必须为纯对象’错误

解决Jest测试中异步Action触发“Actions must be plain objects”错误

我开发的登录页面功能正常:点击登录按钮会dispatch异步action(向服务器发请求),成功跳转首页,失败显示错误。但Jest单元测试模拟点击登录按钮时,触发错误:"Actions must be plain objects. Use custom middleware for async actions."。当前使用所有依赖的最新版本,未手动添加thunk或logger中间件。

相关代码

configureStore.js

// ... persistedReducer 及其他代码不相关,可按需提供
export const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }),
});

login.component.js

//...
const handleSubmit = (event) => {
  const form = event.currentTarget;
  if (form.checkValidity() === false) {
    event.stopPropagation();
  }

  setValidated(true);
  event.preventDefault();

  setLoading(true);
  // 测试时此处触发错误
  dispatch(login(username, password))
    .then(() => {
      router.push("/");
      setLoading(false);
    })
    .catch(() => {
      setLoading(false);
    });
};

return (
  <Form
    noValidate
    validated={validated}
    onSubmit={handleSubmit}
    id="form-login"
  >
    {/* ...表单内容 */}
  </Form>
);

login.component.test.js

//...
jest.mock("next/navigation", () => ({
  useRouter() {
    return {
      prefetch: () => null,
    };
  },
}));

describe("Login Component", () => {
  let store;
  beforeEach(() => {
    const mockStore = configureMockStore(/*middlewares*/); // 尝试方案3时的位置
    store = mockStore({
      auth: false,
      message: "unit-test"
    });
  });

  test("it should have a working login button", async () => {
    const actual = renderLoginComponent(store);
    const mockDispatch = jest.fn();
    jest.mock("react-redux", () => ({
      useSelector: jest.fn(),
      useDispatch: () => mockDispatch,
    }));

    const button = actual.getByRole("button", { name: "Login" });
    fireEvent.click(button); // 触发错误的位置
  });
});

function renderLoginComponent(store) {
  return render(createLoginComponent(store, Login));
}

function createLoginComponent(store, ComponentToTest) {
  return (
    <Provider store={store}>
      <ComponentToTest />
    </Provider>
  );
}

已尝试的无效方案

  • 添加thunk
  • mock dispatch
  • 将默认middleware配置传入configureMockStore方法

有效解决办法

问题根源:测试用的mockStore未配置支持异步Action的中间件(比如Redux Thunk),但真实环境中Redux Toolkit的configureStore默认已集成thunk,所以功能正常。

修改后的测试代码

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { fireEvent, render } from '@testing-library/react';
import { Provider } from 'react-redux';
import Login from './login.component';

jest.mock("next/navigation", () => ({
  useRouter() {
    return {
      prefetch: () => null,
      push: jest.fn(), // 补充mock组件用到的push方法
    };
  },
}));

// 创建集成thunk的mockStore
const mockStore = configureMockStore([thunk]);

describe("Login Component", () => {
  let store;

  beforeEach(() => {
    store = mockStore({
      auth: false,
      message: "unit-test"
    });

    // 提前mock react-redux的hooks,确保组件渲染时使用
    jest.mock("react-redux", () => ({
      useSelector: jest.fn().mockReturnValue(false), // 根据组件逻辑返回对应值
      useDispatch: () => store.dispatch, // 使用mockStore的dispatch处理异步action
    }));
  });

  test("it should have a working login button", async () => {
    const { getByRole } = render(
      <Provider store={store}>
        <Login />
      </Provider>
    );

    const button = getByRole("button", { name: "Login" });
    fireEvent.click(button);

    // 可选:添加断言验证action是否被触发
    const actions = store.getActions();
    expect(actions).toHaveLength(1);
  });
});

关键修改点

  1. 给mockStore添加thunk中间件:redux-mock-store默认不支持异步action,必须手动传入thunk
  2. 调整mock时机:在组件渲染前(beforeEach中)mock react-redux的hooks,避免组件使用原始的dispatch
  3. 补充router的push方法mock:组件登录成功后会调用router.push,需mock避免报错
  4. 统一dispatch来源:使用mockStore的dispatch替代单独的jest.fn(),确保能处理异步action

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:04:50