Hi I am learning React and Redux. I am trying to render a React component to test some user interactions using react test library. The component uses useSelector to get a list of missions from the redux store. I tried to write a reusable test render function as suggested in redux documentation.
This is the element I am trying to render for testing.
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;
This is my 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;
This is how I wrote the helper function to 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;
This is the test case I have written to check a button with text.
test('testing Mission page UI', () => {
renderWithProvider( < MissionsTable / > );
const missionButtons = screen.findAllByText('Join Mission');
expect(missionButtons).toHaveLength(1);
});
I am getting this error. I understand the list is undefined. How can I get the data I added in intialstate in the mockstore to be rendered.
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}
This is how the UI will look like. I have to check user interaction on join mission buttons.
The state data structure is { missions: any[]; toFetch: string }
, so you should get the missions
state slice like:
const list = useSelector(state => state.missions);
E.g.
MissionsTable.jsx
:
import React from 'react';
import { useSelector } from 'react-redux';
const MissionsTable = () => {
const list = useSelector((state) => state.missions);
console.log('list: ', list);
return null;
};
export default MissionsTable;
missionSlice.js
:
import { createSlice } from '@reduxjs/toolkit';
const initialState = { missions: [], toFetch: 'true' };
const missionSlice = createSlice({
name: 'missions',
initialState,
reducers: {},
});
export default missionSlice.reducer;
MissionsTable.test.jsx
:
import { configureStore } from "@reduxjs/toolkit";
import { render } from "@testing-library/react";
import React from "react";
import { Provider } from "react-redux";
import MissionsTable from "./MissionsTable";
import missionSliceReducer from './missionSlice';
function renderWithProvider(
ui,
{
preloadedState = {
missions: [{
id: 'testmission1',
name: 'test1',
descriptions: 'test mission',
isReserved: false,
wiki: '',
}]
},
store = configureStore({ reducer: { missions: missionSliceReducer }, preloadedState }),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>;
}
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }
}
describe('75674010', () => {
test('should pass', () => {
renderWithProvider(<MissionsTable />);
})
})
Test result:
PASS redux-toolkit-example packages/redux-toolkit-example/stackoverflow/75674010/MissionsTable.test.jsx
75674010
✓ should pass (27 ms)
console.log
list: [
{
id: 'testmission1',
name: 'test1',
descriptions: 'test mission',
isReserved: false,
wiki: ''
}
]
at MissionsTable (packages/redux-toolkit-example/stackoverflow/75674010/MissionsTable.jsx:6:11)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.715 s