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

如何在Jest中模拟测试Expo项目的异步Redux Action?

Jest测试Redux异步signIn Action方案

前置依赖安装

先安装测试异步Redux Action需要的工具:

npm install redux-mock-store redux-thunk --save-dev

测试文件编写

新建authActions.test.ts测试文件,完整代码如下:

import configureMockStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import { AnyAction } from 'redux';
// 导入业务代码
import { signIn, loadAuth } from './authActions';
import * as types from './authTypes';
// 导入外部依赖
import Api from '@myPackage/api';
import SecureStore from 'expo-secure-store';
import { Alert } from 'react-native';
import i18n from '@/utils/i18n'; // 替换为你项目实际的i18n导入路径

// 模拟所有外部依赖
jest.mock('@myPackage/api', () => ({
  default: {
    accounts: {
      accountsLogin: jest.fn()
    }
  }
}));
jest.mock('expo-secure-store', () => ({
  setItemAsync: jest.fn()
}));
jest.mock('react-native', () => ({
  Alert: {
    alert: jest.fn()
  }
}));
jest.mock('@/utils/i18n', () => ({
  default: {
    t: jest.fn()
  }
}));
// 模拟同文件的loadAuth Action,避免关联逻辑影响当前测试
jest.mock('./authActions', () => ({
  ...jest.requireActual('./authActions'),
  loadAuth: jest.fn(() => ({ type: 'TEST_LOAD_AUTH' }))
}));

// 初始化mock store,注入thunk中间件支持异步Action
type RootState = Record<string, unknown>; // 替换为你项目实际的RootState类型
const middlewares = [thunk];
const mockStore = configureMockStore<RootState, ThunkDispatch<RootState, void, AnyAction>>(middlewares);

// 每次测试前清空所有mock的调用记录,避免用例互相干扰
beforeEach(() => {
  jest.clearAllMocks();
});

describe('signIn Action 测试', () => {
  const mockLoginParams = {
    email: 'test@example.com',
    phoneNumber: '13800000000',
    password: 'test123456'
  };

  test('登录成功时逻辑正常执行', async () => {
    // 模拟登录接口返回成功
    (Api.accounts.accountsLogin as jest.Mock).mockResolvedValue({
      data: {
        success: true,
        token: 'mock_valid_token_123'
      }
    });

    const store = mockStore({});
    await store.dispatch(signIn(mockLoginParams) as any);

    // 断言接口调用参数正确
    expect(Api.accounts.accountsLogin).toHaveBeenCalledWith(mockLoginParams);
    // 断言token已存入SecureStore
    expect(SecureStore.setItemAsync).toHaveBeenCalledWith('authtoken', 'mock_valid_token_123');
    // 断言派发的动作符合预期
    const dispatchedActions = store.getActions();
    expect(dispatchedActions[0]).toEqual({ type: types.STATE_LOADING });
    expect(loadAuth).toHaveBeenCalled();
  });

  test('接口返回登录失败时正常抛错', async () => {
    // 模拟接口返回success=false
    (Api.accounts.accountsLogin as jest.Mock).mockResolvedValue({
      data: {
        success: false
      }
    });
    // 模拟i18n返回错误文案
    (i18n.t as jest.Mock).mockReturnValue('用户名或密码错误');

    const store = mockStore({});
    await store.dispatch(signIn(mockLoginParams) as any);

    // 断言弹窗触发、错误动作派发
    expect(Alert.alert).toHaveBeenCalledWith('用户名或密码错误');
    const dispatchedActions = store.getActions();
    expect(dispatchedActions[1]).toEqual({
      type: types.AUTH_ERROR,
      payload: '用户名或密码错误'
    });
  });

  test('接口请求异常时正常捕获错误', async () => {
    // 模拟接口直接抛出错误
    const mockErrorMsg = '网络连接超时';
    (Api.accounts.accountsLogin as jest.Mock).mockRejectedValue({
      error: { message: mockErrorMsg }
    });

    const store = mockStore({});
    await store.dispatch(signIn(mockLoginParams) as any);

    expect(Alert.alert).toHaveBeenCalledWith(mockErrorMsg);
    const dispatchedActions = store.getActions();
    expect(dispatchedActions[1]).toEqual({
      type: types.AUTH_ERROR,
      payload: mockErrorMsg
    });
  });
});

注意事项

  • 代码里的导入路径、RootState类型、types常量需要替换为你项目内的实际值
  • 若你的i18n、Api封装结构和示例不同,对应调整mock的结构即可,保证和实际导出结构一致
  • 所有异步Action的dispatch必须加await,否则断言会在逻辑执行完成前触发,导致用例失败

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 03:45:02