如何在未Eject的Expo CRNA项目中实现全量测试(含Redux、Axios等)
嘿,我来帮你搞定这个问题!在未执行 eject 的 Expo Create React Native App(CRNA)项目里跑通组件、Redux Action 和 Reducer 的所有测试,确实需要针对 Expo 生态、Redux-Thunk、Axios 以及 React Native Maps 这些依赖做一些针对性的配置和测试策略调整。下面是一步步的实操方案:
首先确保你的项目已经安装了必要的测试依赖——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" }
因为 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 } }) }));
同步 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); });
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); });
- Expo 模块报错:所有 Expo 提供的原生模块(比如
expo-location、expo-camera)都需要在测试中 mock,否则会因为找不到原生实现而报错。 - Axios 未被正确 mock:确保用
jest.mock('axios')完全 mock Axios,不要只 mock 部分方法,避免真实请求被触发。 - 组件渲染超时:如果测试组件时遇到超时,检查是否有未被 mock 的异步操作(比如组件挂载时调用的 API 请求),需要提前 mock 这些操作。
内容的提问来源于stack exchange,提问作者Thomas Hagström

