React Testing Library异步测试挂起问题排查(Redux+MSW)
问题描述
我正在为使用Mock API数据的React页面运行异步测试,但测试会持续挂起直至达到Jest超时时间。我采用Mock Service Worker(MSW)提供页面数据,该方案在开发环境运行正常,项目同时使用Redux。我遵循Redux团队建议,不Mock Redux以开展完整集成测试,观察页面实际运行状态。Redux Action调用Fetch请求,MSW返回数据后存入Redux Store,但测试始终在await waitFor处超时。
测试代码
test('displays position page', async () => { const container = document.createElement('div'); const root = createRoot(container!); root.render( <ReduxProvider store={store} key="redux"> <MemoryRouter initialEntries={['/']}> <PositionPage /> </MemoryRouter> </ReduxProvider> ) await waitFor(async () => { expect(await screen.getByText("XXXX", { exact: false })).toBeInTheDocument(); }) });
MSW配置
setupTests.ts
import '@testing-library/jest-dom'; import { getServer } from './core/mocks/server'; const server = getServer(); beforeAll(() => { server.listen() }); afterEach(() => { server.resetHandlers() }) afterAll(() => { server.close() })
server.ts
import { setupServer } from 'msw/node'; import type { SetupServer } from 'msw/node'; import { handlers } from './handlers'; // eslint-disable-next-line @typescript-eslint/init-declarations let server: SetupServer | undefined; export const getServer = (): SetupServer => { if (!server) { server = setupServer(...handlers); } return server; };
请问我哪里操作有误?是否因使用真实Redux而非Mock版本导致?能否在保留真实Redux带来的「真实场景」测试特性的前提下解决该问题?
排查方向与解决方案
1. 修正测试查询逻辑的冗余写法
screen.getByText是同步查询方法,不存在时会立即抛出错误,并非异步方法,你在waitFor中使用await screen.getByText属于错误用法,会导致等待逻辑失效。
正确写法:
使用findByText(异步查询方法,内部已封装等待逻辑)替代waitFor+getByText的组合:
test('displays position page', async () => { const container = document.createElement('div'); const root = createRoot(container!); root.render( <ReduxProvider store={store} key="redux"> <MemoryRouter initialEntries={['/']}> <PositionPage /> </MemoryRouter> </ReduxProvider> ) // 直接用findByText等待元素出现 const targetElement = await screen.findByText("XXXX", { exact: false }); expect(targetElement).toBeInTheDocument(); });
2. 确保Redux Store实例隔离
如果多个测试用例复用同一个全局Store实例,之前测试的状态会污染当前测试,导致页面无法正确获取新数据。
解决方式:
在每个测试用例中创建全新的Store实例:
test('displays position page', async () => { // 每个测试都初始化新的Store const store = configureStore({ reducer: rootReducer, // 可根据需求设置初始状态 }); const container = document.createElement('div'); const root = createRoot(container!); root.render( <ReduxProvider store={store} key="redux"> <MemoryRouter initialEntries={['/']}> <PositionPage /> </MemoryRouter> </ReduxProvider> ) const targetElement = await screen.findByText("XXXX", { exact: false }); expect(targetElement).toBeInTheDocument(); });
3. 验证MSW Handler的请求匹配
检查handlers是否正确匹配Redux Action中发起的请求,包括URL、HTTP方法、请求头是否完全一致。开发环境正常但测试失败,可能是测试环境的请求URL存在差异(比如baseURL配置不同)。
验证方法:
在Handler中添加日志,确认请求是否被MSW拦截:
// handlers.ts示例 import { rest } from 'msw'; export const handlers = [ rest.get('/api/positions', (req, res, ctx) => { console.log('请求已被MSW捕获'); // 测试时查看控制台是否输出该日志 return res(ctx.json(mockPositionData)); }) ];
如果无日志输出,说明请求未被拦截,需调整Handler的URL匹配规则。
4. 排查React 18 Strict Mode的影响
React 18的Strict Mode会在测试环境中触发组件双重渲染,若你的Redux逻辑或组件在双重渲染时出现异常(比如重复发起请求但状态更新异常),可能导致超时。
验证方式:
暂时移除Strict Mode,观察测试是否恢复正常:
root.render( // 移除<StrictMode>包裹 <ReduxProvider store={store} key="redux"> <MemoryRouter initialEntries={['/']}> <PositionPage /> </MemoryRouter> </ReduxProvider> )
若移除后测试正常,需调整组件或Redux逻辑以兼容双重渲染。
关于真实Redux的疑问
使用真实Redux并非超时的原因,Redux团队推荐的集成测试方式完全可行。只要保证Store实例隔离、请求被正确Mock、测试查询逻辑正确,就能在保留真实场景测试特性的前提下解决问题。
内容的提问来源于stack exchange,提问作者Rob

