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); }); });
关键修改点
- 给mockStore添加thunk中间件:redux-mock-store默认不支持异步action,必须手动传入thunk
- 调整mock时机:在组件渲染前(beforeEach中)mock react-redux的hooks,避免组件使用原始的dispatch
- 补充router的push方法mock:组件登录成功后会调用router.push,需mock避免报错
- 统一dispatch来源:使用mockStore的dispatch替代单独的jest.fn(),确保能处理异步action
内容的提问来源于stack exchange,提问作者Liviu Sosu
相关产品推荐
相关产品推荐

