如何模拟测试Redux-Thunk Action?异步Action存桩测试报错求助
解决异步Redux Action测试中存桩内部Action的问题
首先,你遇到的Error: Actions must be plain objects. Use custom middleware for async actions.错误,核心原因无非两个:要么是你的测试Store没配置处理异步Action的中间件(比如redux-thunk),要么是你对postRequest的存桩不符合异步Action的结构要求。下面一步步给你梳理解决办法:
1. 先确保测试Store正确配置异步中间件
异步Redux Action(比如返回函数的thunk Action)必须依赖中间件才能被Redux识别处理。如果你的测试环境里的Store没应用redux-thunk(或你用的其他异步中间件),直接dispatch异步Action就会抛出这个错误。
给你一个测试Store的配置示例:
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from '../path/to/rootReducer'; // 测试用Store必须和生产环境保持一致的中间件配置 export const createTestStore = (initialState = {}) => { return createStore( rootReducer, initialState, applyMiddleware(thunk) ); };
2. 正确存桩异步Action postRequest
postRequest作为异步Action,本质是返回一个函数(比如thunk函数)。你存桩时必须保证它的返回值符合这个结构,不能直接返回普通对象或undefined。
用Jest存桩的常见示例
假设你的postRequest原本逻辑是这样的:
export const postRequest = (payload) => async (dispatch) => { // 真实的异步请求逻辑 dispatch({ type: 'POST_REQUEST_SUCCESS', payload }); };
存桩时,你需要让它返回一个能被thunk处理的函数,比如两种常用方式:
// 方式1:存桩后返回空thunk函数,完全跳过原逻辑 jest.mock('../path/to/actions', () => ({ ...jest.requireActual('../path/to/actions'), // 保留其他未存桩的Action postRequest: jest.fn(() => () => {}) // 关键:返回一个空函数 })); // 方式2:存桩后dispatch测试用Action,方便后续断言验证 jest.mock('../path/to/actions', () => ({ ...jest.requireActual('../path/to/actions'), postRequest: jest.fn((payload) => (dispatch) => { dispatch({ type: 'STUBBED_POST_REQUEST', payload }); }) }));
关键注意事项
- 绝对不能让存桩的
postRequest直接返回普通对象,比如jest.fn(() => ({ type: 'POST_SUCCESS' }))——这会导致父Action里dispatch(postRequest())时,Redux接收到的是不符合异步逻辑的普通对象,既不符合原代码逻辑,还可能引发其他测试问题。 - 如果你的异步Action用的是redux-saga这类中间件,存桩时要对应返回generator函数,但你的错误提示更偏向redux-thunk场景。
3. 编写测试用例并验证存桩效果
存桩完成后,你可以在测试用例里验证postRequest是否被正确调用,以及dispatch的Action是否符合预期:
import { createTestStore } from '../path/to/testStore'; import { myActionThatCallsPostRequest } from '../path/to/yourAction'; import { postRequest } from '../path/to/actions'; test('父Action应该正确调用存桩的postRequest', async () => { const testStore = createTestStore(); const testPayload = { id: 123 }; // 触发要测试的Action await testStore.dispatch(myActionThatCallsPostRequest(testPayload)); // 断言postRequest被调用,且参数正确 expect(postRequest).toHaveBeenCalledTimes(1); expect(postRequest).toHaveBeenCalledWith(testPayload); // 如果用了方式2的存桩,可以断言dispatch的测试Action const dispatchedActions = testStore.getActions(); expect(dispatchedActions).toContainEqual({ type: 'STUBBED_POST_REQUEST', payload: testPayload }); });
排查常见坑点
- 如果你用的是Mocha+Sinon这类测试框架,存桩逻辑类似:要保证
postRequest返回一个函数。比如Sinon的写法:sinon.stub(actions, 'postRequest').returns(() => () => {}); - 检查父Action的逻辑:确保你是在异步函数里
dispatch(postRequest()),而不是直接返回postRequest()——如果直接返回,存桩错误也会导致同样的报错。
内容的提问来源于stack exchange,提问作者Vytautas Butkus
相关产品推荐
相关产品推荐

