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

如何在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过滤得到的。测试时存在两个关键问题:

  1. 路由切换后,useParams()没有拿到能匹配ArtifactArray的有效name参数,导致filter返回空数组,取第0项得到undefined,最终传给Provider的value是undefined
  2. 你在测试代码外层包裹的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 06:12:01