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

使用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数组的对象)

修复步骤

  1. 修正preloadedState结构
    修改renderWithProviders里的预加载状态,让它匹配slice的状态结构,同时修正字段拼写错误:

    preloadedState = {
      missions: { // 对应reducer的键,值为slice的完整状态对象
        missions: [{
          id: 'testmission1',
          name: 'test1',
          description: 'test mission', // 修正descriptions为description
          isReserved: false,
          wiki: '',
        }],
        toFetch: false // 补充slice中的其他状态字段
      }
    },
    
  2. 修正测试用例中的错误

    • 拼写错误:renderWithProvider改为renderWithProviders
    • findAllByText是异步查询方法,必须用await,测试函数需标记为async:
    test('testing Mission page UI', async () => {
      renderWithProviders(<MissionsTable />);
      const missionButtons = await screen.findAllByText('Join Mission');
      expect(missionButtons).toHaveLength(1);
    });
    
  3. 可选:给组件添加空值保护
    为避免生产环境出现类似报错,可在组件中给list添加默认空数组:

    const list = useSelector((state) => state.missions.missions) || [];
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 12:18:15