Redux AsyncThunk测试异常:状态未更新致UI测试失败
问题:Redux状态未被MSW模拟的API数据更新,组件测试失败
我正在学习React与Redux,尝试对一个会分发action从外部API获取数据的React组件进行集成测试。使用MSW模拟fetch请求,同时采用Redux官方文档中的可复用测试渲染函数。自定义渲染组件后,预期API返回的模拟数据会更新到Redux状态并在MissionsTable组件中渲染,但目前store并未被API返回的模拟数据更新。已确认MSW的API模拟能返回设定的测试数据,求指点解决方向。
测试文件
import { screen } from '@testing-library/react'; import renderWithProvider from '../utils/test-util'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import MissionsPage from '../components/missions/MissionsPage'; const missions = [ { id: 'testmission1', name: 'test1', descriptions: 'test mission', isReserved: false, wiki: '', }, ]; export const handlers = [ rest.get('https://api.spacexdata.com/v3/missions', (req, res, ctx) => { return res(ctx.json(missions)); }), ]; const server = setupServer(...handlers); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); test('testing Mission page UI', async () => { const { store } = renderWithProvider(<MissionsPage />); console.log(store.getState().missions); const text = screen.getByText('/test mission/i'); expect(await text.toBeInTheDocument()); });
待渲染组件(MissionsPage)
import { useDispatch, useSelector } from 'react-redux'; import { useEffect } from 'react'; import MissionsTable from './MissionsTable'; import { fetchMissions } from '../../redux/missions/missionSlice'; const MissionsPage = () => { const dispatch = useDispatch(); const status = useSelector((state) => state.missions.toFetch); useEffect(() => { if (status) { dispatch(fetchMissions()); } }, [status, dispatch]); return ( <MissionsTable /> ); }; export default MissionsPage;
可复用测试渲染函数
import React from 'react'; import { render } from '@testing-library/react'; import { configureStore } from '@reduxjs/toolkit'; import { Provider } from 'react-redux'; import missionSliceReducer from '../redux/missions/missionSlice'; function renderWithProviders( ui, { preloadedState = {}, store = configureStore({ reducer: { missions: missionSliceReducer }, preloadedState, }), ...renderOptions } = {} ) { function Wrapper({ children }) { return <Provider store={store}>{children}</Provider>; } return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }; } export default renderWithProviders;
定义AsyncThunk的Slice
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) { misObj.isReserved = !misObj.isReserved; } return misObj; }); state.missions = newArr; }, }, extraReducers(builder) { builder.addCase(fetchMissions.fulfilled, (state, action) => { const missionList = action.payload.map(misObj => ({ id: misObj.mission_id, name: misObj.mission_name, description: misObj.description, wiki: misObj.wikipedia, isReserved: false, })); state.missions = missionList; state.toFetch = false; }); }, }); export const { bookMission } = missionSlice.actions; export default missionSlice.reducer;
报错信息
console.log { missions: [], toFetch: 'true' } at Object.<anonymous> (src/__tests__/missionUI.test.js:31:11) FAIL src/__tests__/missionUI.test.js ✕ testing Mission page UI (85 ms) ● testing Mission page UI TestingLibraryElementError: Unable to find an element with the text: /test mission/i. <body> <div> <div> <div class="table-responsive-sm"> <table class="missionTable table table-striped table-bordered table-hover"> <thead> <tr> <th>Mission</th> <th>Description</th> <th>Status</th> <th></th> </tr> </thead> <tbody /> </table> </div> </div> </div> </body> 30 | const { store } = renderWithProvider(<MissionsPage />); 31 | console.log(store.getState().missions); > 32 | const text = screen.getByText('/test mission/i'); | ^ 33 | expect(await text.toBeInTheDocument()); 34 | });
关键问题及修复方向
- 初始状态类型不一致:Slice中
toFetch初始值是字符串'true',但后续更新为布尔值false,类型混乱可能导致逻辑异常。统一改为布尔值:const initialState = { missions: [], toFetch: true }; - MSW返回数据与Slice处理不匹配:测试中MSW返回的字段是
descriptions,但Slice里取的是description;且Slice依赖真实API的mission_id、mission_name字段,测试数据里没有这些键,导致映射后生成空数组。要么调整MSW返回数据结构适配Slice,要么修改Slice映射逻辑适配测试数据。 - 未等待异步操作完成:测试中直接用同步的
getByText查找元素,此时异步请求还未完成。改用异步的findByText并等待:const text = await screen.findByText(/test mission/i); - 正则表达式写法错误:
screen.getByText('/test mission/i')是把字符串当文本查找,正确写法是去掉引号:screen.getByText(/test mission/i) - 断言写法错误:
expect(await text.toBeInTheDocument())中text已经是await后的结果,直接断言即可:expect(text).toBeInTheDocument();
修复后测试代码示例
test('testing Mission page UI', async () => { const { store } = renderWithProvider(<MissionsPage />); const text = await screen.findByText(/test mission/i); expect(text).toBeInTheDocument(); expect(store.getState().missions).toHaveLength(1); });
内容的提问来源于stack exchange,提问作者Ram Kumar
相关产品推荐
相关产品推荐

