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

Vite+TS+Redux项目Vitest测试App组件时Async Thunk报错求助

解决Vitest测试Redux Async Thunk时的"Actions must be plain objects"错误

问题重现

在Vite+TypeScript+Redux Toolkit项目中,使用Vitest测试App组件渲染时,调用dispatch(fetchAirports())触发异步Thunk时抛出错误:

Error: Actions must be plain objects. Use custom middleware for async actions

报错位置在App.tsx的dispatch(fetchAirports())调用处。

原因分析

这个错误的核心是测试环境中使用的Redux Store未加载Redux Toolkit内置的thunk中间件。Redux本身不支持异步action,Redux Toolkit的configureStore默认已包含thunk中间件,但如果测试时手动创建未正确配置的store实例,就会丢失该中间件,导致无法处理async thunk。

解决方案

1. 确保Redux Store配置正确

确认你的store配置文件(如store.ts)使用Redux Toolkit的configureStore创建,而非原生Redux的createStore:

// store.ts
import { configureStore } from '@reduxjs/toolkit';
import airportsReducer from './features/airports/airportsSlice';

export const store = configureStore({
  reducer: {
    airports: airportsReducer,
  },
  // configureStore默认已包含thunk,无需手动添加
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

2. 测试文件中正确配置Store

在Vitest测试文件中,通过configureStore创建测试专用store,确保thunk中间件被加载:

// App.test.tsx
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import App from './App';
import airportsReducer from './features/airports/airportsSlice';

describe('App Component', () => {
  it('renders without crashing', () => {
    const testStore = configureStore({
      reducer: {
        airports: airportsReducer,
      },
    });

    render(
      <Provider store={testStore}>
        <App />
      </Provider>
    );
  });
});

3. 模拟Async Thunk(孤立测试推荐)

若不想触发真实API请求,可模拟async thunk的返回:

// App.test.tsx
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import App from './App';
import airportsReducer from './features/airports/airportsSlice';
import { fetchAirports } from './features/airports/airportsSlice';

// 模拟fetchAirports thunk
jest.mock('./features/airports/airportsSlice', () => ({
  ...jest.requireActual('./features/airports/airportsSlice'),
  fetchAirports: jest.fn().mockReturnValue({ type: 'mock/action' }),
}));

describe('App Component', () => {
  it('dispatches fetchAirports on mount', () => {
    const testStore = configureStore({
      reducer: {
        airports: airportsReducer,
      },
    });
    const spyDispatch = jest.spyOn(testStore, 'dispatch');

    render(
      <Provider store={testStore}>
        <App />
      </Provider>
    );

    expect(spyDispatch).toHaveBeenCalledWith(fetchAirports());
  });
});

4. 组件中使用正确的Dispatch类型

在组件中指定AppDispatch类型,避免隐性类型错误:

// App.tsx
import { useDispatch } from 'react-redux';
import { useEffect } from 'react';
import { AppDispatch } from './store';
import { fetchAirports } from './features/airports/airportsSlice';

function App() {
  const dispatch = useDispatch<AppDispatch>();

  useEffect(() => {
    dispatch(fetchAirports());
  }, [dispatch]);

  return (
    // 组件内容
  );
}

export default App;

关键检查点

  • 所有测试用Store必须通过configureStore创建,禁止使用原生createStore
  • 不要随意移除Redux Toolkit的默认中间件
  • 模拟async thunk时,确保返回符合Redux规范的action对象

内容的提问来源于stack exchange,提问作者Ilia Popov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 02:45:11