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

如何模拟测试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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:21:00