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

如何使用Jest测试包含createAsyncThunk的Redux Toolkit slice

Redux Toolkit 含 createAsyncThunk 的 Slice 测试方案

你遇到的超时问题核心原因有两个:一是未 mock 服务层/网络请求,测试环境发起真实 HTTP 请求导致超时;二是异步 thunk 执行时未等待流程结束就做断言,或 slice 本身未编写对应 thunk 状态的 reducer 处理逻辑。

首先先修正你贴出代码里的遗漏项:当前combineExtraReducers仅处理了getData.fulfilled状态,完全没有添加postData对应的pending/fulfilled/rejected状态处理,这会导致即使thunk执行成功,store状态也不会更新。补全逻辑参考:

const combineExtraReducers = (
  builder: ActionReducerMapBuilder<MyState>,
) => {
  builder.addCase(getData.fulfilled, (state, { payload }) => {
    state.data = payload;
  });
  // 补全postData的状态处理
  builder
    .addCase(postData.fulfilled, (state, { payload }) => {
      state.data = payload;
    })
    .addCase(postData.rejected, (_, { payload }) => {
      // 按业务需求处理错误,比如存储错误信息
      console.error('提交失败', payload);
    });
};

接下来分两种场景写测试,第一种是纯reducer测试(无异步逻辑,完全不会出现超时问题,适合验证状态更新逻辑),第二种是thunk集成测试(验证完整调用流程)。

1. 纯Reducer测试(最简单,无异步)

这种方式不需要运行thunk的异步逻辑,直接手动构造thunk生成的action对象传入reducer即可,全程同步执行:

import { dataSlice, postData } from './对应slice文件路径';

describe('dataSlice 状态逻辑', () => {
  const initialState = { data: [] };

  it('处理postData.fulfilled时应正确更新data字段', () => {
    const mockPayload = [{ id: 1, content: '测试数据' }];
    const action = {
      type: postData.fulfilled.type,
      payload: mockPayload
    };
    const nextState = dataSlice.reducer(initialState, action);
    expect(nextState.data).toEqual(mockPayload);
  });

  it('处理postData.rejected时应按预期处理错误', () => {
    const mockErrorMsg = '提交失败';
    const action = {
      type: postData.rejected.type,
      payload: mockErrorMsg
    };
    const nextState = dataSlice.reducer(initialState, action);
    // 按你的错误处理逻辑写断言即可
  });
});

2. Thunk 集成测试(验证完整流程)

如果需要验证thunk内部的服务调用、参数传递、错误分支逻辑,需要配置测试用store,mock掉服务层方法,并且必须await dispatch的执行结果,否则异步逻辑未执行完测试就会结束,触发超时或断言失败:

import { configureStore } from '@reduxjs/toolkit';
import { dataSlice, postData } from './对应slice文件路径';
import { MyService } from './MyService对应路径';

// mock整个MyService类,不发起真实请求
jest.mock('./MyService对应路径', () => ({
  MyService: jest.fn().mockImplementation(() => ({
    postData: jest.fn()
  }))
}));

// 生成测试用store的工具函数
const createTestStore = () => configureStore({
  reducer: {
    data: dataSlice.reducer
  }
});

describe('postData 异步thunk', () => {
  let mockPostData: jest.Mock;
  beforeEach(() => {
    // 每个用例前清空mock记录,避免用例间互相影响
    jest.clearAllMocks();
    // 获取mock实例上的postData方法
    const serviceInstance = (MyService as jest.Mock).mock.instances[0];
    mockPostData = serviceInstance.postData as jest.Mock;
  });

  it('请求成功时应调用服务并更新store状态', async () => {
    const mockResult = [{ id: 2, content: '服务返回数据' }];
    const testParam = { data: [{ content: '提交的测试数据' }] };
    // 配置mock返回成功结果
    mockPostData.mockResolvedValue(mockResult);
    const store = createTestStore();

    // 核心:必须加await等待thunk全流程执行完成
    await store.dispatch(postData(testParam));

    // 验证服务被正确调用
    expect(mockPostData).toHaveBeenCalledWith(testParam.data);
    // 验证store状态更新正确
    expect(store.getState().data.data).toEqual(mockResult);
  });

  it('请求失败时应返回rejectWithValue包装的错误信息', async () => {
    const testError = new Error('网络连接异常');
    const testParam = { data: [] };
    // 配置mock返回失败
    mockPostData.mockRejectedValue(testError);
    const store = createTestStore();

    const dispatchResult = await store.dispatch(postData(testParam));
    // 验证thunk走了rejected分支
    expect(dispatchResult.type).toBe(postData.rejected.type);
    // 验证错误信息正确
    expect(dispatchResult.payload).toBe('网络连接异常');
  });
});

超时问题排查点

  • 确认await store.dispatch(thunkAction)没有遗漏,不加await会导致测试用例提前结束,异步逻辑未执行完成。
  • 确认服务层/axios被正确mock,没有发起真实网络请求。如果直接mock axios,写法和mock MyService一致,jest.mock('axios')后给对应请求方法配置mock返回值即可。
  • 检查thunk内部是否有未捕获的同步异常,比如MyService初始化时抛出错误会导致流程中断。
  • 若mock逻辑较复杂,可适当延长单测超时时间,比如在用例末尾加, 10000将超时时间改为10秒,正常mock正确的场景不需要调整该配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 05:31:07