如何在Jest中Mock React Context?测试组件遇解构错误求助
测试React Context组件时的解构错误问题
我正在测试一个使用React Context的React组件,遇到了Context消费时的解构错误,具体情况如下:
提供者组件代码
const ArtifactsResumeContext = createContext<Artifact>({} as Artifact); export const useArtifactsResumeContext = () => useContext(ArtifactsResumeContext); const ArtifactsResume = () => { useCallOnloadAnimation() const { name } = useParams(); const artifact = ArtifactArray.flat().filter( (artifact) => artifact.name === name )[0]; return ( <ColumnedFlex sx={{ backgroundColor: '#f4f4f4', gap: 10, }} > <PrevPageArrow /> <ArtifactsResumeContext.Provider value={artifact}> <ArtifactsResumeTitle /> <ArtifactsResumeContent /> </ArtifactsResumeContext.Provider> </ColumnedFlex> ); }; export default ArtifactsResume;
消费者组件代码
const ArtifactsResumeTitle = () => { const { name } = useArtifactsResumeContext(); return ( <IsLoading> <Typography color='primary' variant='h1' fontSize={50} mt={10} > {name} </Typography> </IsLoading> ); }; export default ArtifactsResumeTitle;
测试代码
test('Block Link works properly', () => { render( <Provider store={AppStore()}> <MemoryRouter> <ArtifactsResumeContext.Provider value={BodyArtifacts[0]} > <ArtifactsBlock src='' name='Christian' /> <AppRouter /> </ArtifactsResumeContext.Provider> </MemoryRouter> </Provider> ); fireEvent.click(screen.getAllByRole('link', { hidden: true })[0]) screen.debug() });
错误信息
TypeError: Cannot destructure property 'name' of '(0 , _ArtifactsResume.useArtifactsResumeContext)(...)' as it is undefined.
4 | 5 | const ArtifactsResumeTitle = () => { > 6 | const { name } = useArtifactsResumeContext(); | ^ 7 | return ( 8 | <IsLoading> 9 | <Typography*
我尝试过Mock Context并将ArtifactsBlock包裹在Provider中,但均无效,请问问题出在哪?
问题分析与解决方法
核心问题
点击链接后路由切换到ArtifactsResume组件,但该组件内部的Context Provider的value是通过useParams()获取name后,从ArtifactArray过滤得到的。测试时存在两个关键问题:
- 路由切换后,
useParams()没有拿到能匹配ArtifactArray的有效name参数,导致filter返回空数组,取第0项得到undefined,最终传给Provider的value是undefined - 你在测试代码外层包裹的
ArtifactsResumeContext.Provider完全没用——因为路由切换后渲染的ArtifactsResume组件内部会自己创建一个内层的Provider,内层Provider会覆盖外层的,而内层的value是undefined,所以消费组件解构name时直接报错。
具体解决步骤
方法1:确保路由参数正确匹配
修改测试代码的MemoryRouter,设置initialEntries让路由切换后能拿到正确的name参数,同时确保ArtifactArray中存在对应数据:
test('Block Link works properly', () => { // 假设BodyArtifacts[0].name是'Christian' const testName = BodyArtifacts[0].name; render( <Provider store={AppStore()}> <MemoryRouter initialEntries={[`/your-artifacts-path/${testName}`]}> <ArtifactsBlock src='' name={testName} /> <AppRouter /> </MemoryRouter> </Provider> ); fireEvent.click(screen.getAllByRole('link', { hidden: true })[0]) // 此时应该能正确渲染,不会报错 expect(screen.getByText(testName)).toBeInTheDocument(); });
方法2:Mock useParams返回指定值
如果不想依赖ArtifactArray的测试数据,可以直接MockuseParams钩子:
import { useParams } from 'react-router-dom'; // 在测试文件顶部Mock useParams jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: jest.fn(), })); test('Block Link works properly', () => { const testArtifact = BodyArtifacts[0]; // 让useParams返回正确的name (useParams as jest.Mock).mockReturnValue({ name: testArtifact.name }); render( <Provider store={AppStore()}> <MemoryRouter> <ArtifactsBlock src='' name={testArtifact.name} /> <AppRouter /> </MemoryRouter> </Provider> ); fireEvent.click(screen.getAllByRole('link', { hidden: true })[0]) expect(screen.getByText(testArtifact.name)).toBeInTheDocument(); });
方法3:优化ArtifactsResume组件的容错逻辑
为了避免类似问题,也可以在ArtifactsResume组件里添加容错,确保artifact不会是undefined:
const ArtifactsResume = () => { useCallOnloadAnimation() const { name } = useParams(); const artifact = ArtifactArray.flat().find( (artifact) => artifact.name === name ) || {} as Artifact; // 添加默认值兜底 return ( <ColumnedFlex sx={{ backgroundColor: '#f4f4f4', gap: 10, }} > <PrevPageArrow /> <ArtifactsResumeContext.Provider value={artifact}> <ArtifactsResumeTitle /> <ArtifactsResumeContent /> </ArtifactsResumeContext.Provider> </ColumnedFlex> ); };
内容的提问来源于stack exchange,提问作者Pav
相关产品推荐
相关产品推荐

