使用React Testing Library测试React-Redux组件时map报错排查
解决React Redux组件测试中useSelector获取列表为undefined的问题
我正在学习React和Redux,尝试用React Testing Library测试通过useSelector从Redux Store获取任务列表的MissionsTable组件交互。按照Redux文档写了renderWithProviders函数模拟Store,但运行测试时出现TypeError: Cannot read properties of undefined (reading 'map'),组件里的missions列表是undefined,无法渲染预加载的测试数据,需要解决这个问题来测试“Join Mission”按钮的交互。
相关代码
MissionsTable组件
const MissionsTable = () => { const list = useSelector((state) => state.missions.missions); return ( <div> <Table striped bordered hover responsive="sm" className={styles.missionTable} > <thead> <tr> <th>Mission</th> <th>Description</th> <th>Status</th> <th> </th> </tr> </thead> <tbody> {list.map((obj) => ( <Mission key={obj.id} id={obj.id} name={obj.name} description={obj.description} isReserved={obj.isReserved} /> ))} </tbody> </Table> </div> ); }; export default MissionsTable;
missionSlice
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; const initialState = { missions: [], toFetch: 'true' }; export const fetchMissions = createAsyncThunk('missions/fetchMissions', async () => { const response = await fetch('https://api.spacexdata.com/v3/missions'); return response.json(); }); const missionSlice = createSlice({ name: 'missions', initialState, reducers: { bookMission: (state, { payload }) => { const newArr = state.missions.map((misObj) => { if (misObj.id === payload) { if (misObj.isReserved === true) { return { ...misObj, isReserved: false }; } return { ...misObj, isReserved: true }; } return ({ ...misObj }); }); const newState = { ...state }; return { ...newState, missions: newArr }; }, }, extraReducers(builder) { builder.addCase(fetchMissions.fulfilled, (state, action) => { const newState = { ...state }; const missionList = []; action.payload.forEach((misObj) => { missionList.push({ id: misObj.mission_id, name: misObj.mission_name, description: misObj.description, wiki: misObj.wikipedia, isReserved: false, }); }); newState.missions = missionList; newState.toFetch = false; return newState; }); }, }); export const { bookMission } = missionSlice.actions; export default missionSlice.reducer;
mock Store辅助函数
function renderWithProviders( ui, { preloadedState = { missions: [{ id: 'testmission1', name: 'test1', descriptions: 'test mission', isReserved: false, wiki: '', }] }, store = configureStore({ reducer: { missions: missionSliceReducer }, preloadedState }), ...renderOptions } = {} ) { console.log(preloadedState); function Wrapper({ children }) { return <Provider store={store}>{children}</Provider> } return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) } } export default renderWithProviders;
测试用例
test('testing Mission page UI', () => { renderWithProvider( < MissionsTable / > ); const missionButtons = screen.findAllByText('Join Mission'); expect(missionButtons).toHaveLength(1); });
错误信息
TypeError: Cannot read properties of undefined (reading 'map') 24 | </thead> 25 | <tbody> > 26 | {list.map((obj) => ( | ^ 27 | <Mission 28 | key={obj.id} 29 | id={obj.id}
问题根源
你的renderWithProviders里的preloadedState结构错误,和Redux Store的实际状态结构不匹配:
- missionSlice的初始状态是一个对象,包含
missions数组和toFetch字段:{ missions: [], toFetch: 'true' } - 而你在preloadedState里直接把
missions设为数组,导致Store中state.missions.missions变成undefined(此时state.missions是你预加载的数组,而不是包含missions数组的对象)
修复步骤
修正preloadedState结构
修改renderWithProviders里的预加载状态,让它匹配slice的状态结构,同时修正字段拼写错误:preloadedState = { missions: { // 对应reducer的键,值为slice的完整状态对象 missions: [{ id: 'testmission1', name: 'test1', description: 'test mission', // 修正descriptions为description isReserved: false, wiki: '', }], toFetch: false // 补充slice中的其他状态字段 } },修正测试用例中的错误
- 拼写错误:
renderWithProvider改为renderWithProviders findAllByText是异步查询方法,必须用await,测试函数需标记为async:
test('testing Mission page UI', async () => { renderWithProviders(<MissionsTable />); const missionButtons = await screen.findAllByText('Join Mission'); expect(missionButtons).toHaveLength(1); });- 拼写错误:
可选:给组件添加空值保护
为避免生产环境出现类似报错,可在组件中给list添加默认空数组:const list = useSelector((state) => state.missions.missions) || [];
内容的提问来源于stack exchange,提问作者Ram Kumar
相关产品推荐
相关产品推荐

