如何在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
相关产品推荐
相关产品推荐

