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

如何在未Eject的Expo CRNA项目中实现全量测试(含Redux、Axios等)

嘿,我来帮你搞定这个问题!在未执行 eject 的 Expo Create React Native App(CRNA)项目里跑通组件、Redux Action 和 Reducer 的所有测试,确实需要针对 Expo 生态、Redux-Thunk、Axios 以及 React Native Maps 这些依赖做一些针对性的配置和测试策略调整。下面是一步步的实操方案:

1. 先把基础测试环境搭好

首先确保你的项目已经安装了必要的测试依赖——Expo 项目默认可能自带 jest,但还需要补充几个库来简化 React Native 组件和 Redux 的测试:

npm install --save-dev @testing-library/react-native @testing-library/jest-native redux-mock-store

然后在 package.json 里确认测试脚本配置:

"scripts": {
  "test": "jest"
}
2. 组件测试:重点处理 Expo Maps 和原生依赖

因为 React Native Maps 是原生组件,Jest 的 Node.js 环境没法直接渲染它,所以必须用 mock 替代真实组件。

举个例子,如果你有一个用到 MapView 和 Marker 的组件 DeliveryMap.js,测试文件可以这么写:

import { render, screen } from '@testing-library/react-native';
import DeliveryMap from '../DeliveryMap';

// Mock 整个 react-native-maps 模块
jest.mock('react-native-maps', () => ({
  MapView: jest.fn(() => null), // 替换成空组件,避免渲染错误
  Marker: jest.fn(() => null),
  // 如果你用到了其他 Maps 组件(比如 Polyline),也要一起 mock
}));

test('renders delivery map with location label', () => {
  render(<DeliveryMap />);
  
  // 不用断言 MapView 本身(因为已经被 mock 了),专注测试组件的业务逻辑和其他 UI 元素
  expect(screen.getByText('Your Delivery Spot')).toBeTruthy();
});

如果你的组件还用到了 Expo 提供的其他原生模块(比如 expo-location),同样要 mock 它们的方法:

jest.mock('expo-location', () => ({
  getCurrentPositionAsync: jest.fn().mockResolvedValue({
    coords: { lat: 40.7128, lng: -74.0060 }
  })
}));
3. Redux Action 测试:同步+异步 Thunk 全覆盖

同步 Action 测试

同步 Action 最简单,直接调用 Action Creator,断言返回的 Action 对象是否符合预期:

import { setDeliveryLocation } from '../actions/locationActions';
import { SET_DELIVERY_LOCATION } from '../actions/types';

test('setDeliveryLocation returns correct action', () => {
  const mockCoords = { lat: 40.7128, lng: -74.0060 };
  const action = setDeliveryLocation(mockCoords);
  
  expect(action.type).toBe(SET_DELIVERY_LOCATION);
  expect(action.payload).toEqual(mockCoords);
});

异步 Thunk Action 测试(结合 Axios)

异步 Action 需要用 redux-mock-store 来模拟 Redux Store,同时 mock Axios 的请求:

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import axios from 'axios';
import { fetchNearbyStores } from '../actions/storeActions';
import { FETCH_STORES_REQUEST, FETCH_STORES_SUCCESS } from '../actions/types';

// 配置带 Thunk 中间件的 mock store
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

// Mock Axios
jest.mock('axios');

test('fetchNearbyStores dispatches success actions on API call', async () => {
  // 模拟 Axios 成功响应
  const mockStores = [{ id: 1, name: 'Downtown Store' }];
  axios.get.mockResolvedValue({ data: mockStores });
  
  // 定义预期的 Action 序列
  const expectedActions = [
    { type: FETCH_STORES_REQUEST },
    { type: FETCH_STORES_SUCCESS, payload: mockStores }
  ];
  
  // 创建 mock store 并 dispatch Action
  const store = mockStore({ stores: [] });
  await store.dispatch(fetchNearbyStores({ lat: 40.7128, lng: -74.0060 }));
  
  // 断言 dispatch 的 Action 是否符合预期
  expect(store.getActions()).toEqual(expectedActions);
  // 断言 Axios 调用的 URL 和参数是否正确
  expect(axios.get).toHaveBeenCalledWith('/api/stores/nearby', {
    params: { lat: 40.7128, lng: -74.0060 }
  });
});

// 别忘了测试错误场景
test('fetchNearbyStores dispatches error action on API failure', async () => {
  const mockError = new Error('API down');
  axios.get.mockRejectedValue(mockError);
  
  const expectedActions = [
    { type: FETCH_STORES_REQUEST },
    { type: FETCH_STORES_FAILURE, payload: mockError.message }
  ];
  
  const store = mockStore({ stores: [] });
  await store.dispatch(fetchNearbyStores({ lat: 40.7128, lng: -74.0060 }));
  
  expect(store.getActions()).toEqual(expectedActions);
});
4. Reducer 测试:纯函数的简单验证

Reducer 是纯函数,测试逻辑非常直接——给定初始状态和 Action,断言返回的新状态是否正确:

import storeReducer from '../reducers/storeReducer';
import { FETCH_STORES_REQUEST, FETCH_STORES_SUCCESS } from '../actions/types';

test('handles FETCH_STORES_REQUEST', () => {
  const initialState = {
    list: [],
    loading: false,
    error: null
  };
  
  const action = { type: FETCH_STORES_REQUEST };
  const newState = storeReducer(initialState, action);
  
  expect(newState.loading).toBe(true);
  expect(newState.error).toBe(null);
});

test('handles FETCH_STORES_SUCCESS', () => {
  const initialState = {
    list: [],
    loading: true,
    error: null
  };
  
  const mockStores = [{ id: 1, name: 'Downtown Store' }];
  const action = { type: FETCH_STORES_SUCCESS, payload: mockStores };
  const newState = storeReducer(initialState, action);
  
  expect(newState.loading).toBe(false);
  expect(newState.list).toEqual(mockStores);
});
5. 常见问题排查
  • Expo 模块报错:所有 Expo 提供的原生模块(比如 expo-location、expo-camera)都需要在测试中 mock,否则会因为找不到原生实现而报错。
  • Axios 未被正确 mock:确保用 jest.mock('axios') 完全 mock Axios,不要只 mock 部分方法,避免真实请求被触发。
  • 组件渲染超时:如果测试组件时遇到超时,检查是否有未被 mock 的异步操作(比如组件挂载时调用的 API 请求),需要提前 mock 这些操作。

内容的提问来源于stack exchange,提问作者Thomas Hagström

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:26:55