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

React Context正常但react-testing-library测试Snackbar失败

问题根因

测试失败核心是两个硬伤,和等待时长无关:

  • 测试环境中根本没有渲染Snackbar组件:业务逻辑里Snackbar是挂载在Home组件下的,你写测试时只渲染了CompanySettings组件,自己mock了一个静态的SnackbarContext,其中setSnackbar是空函数,既不会更新状态,也没有任何地方消费Context值去渲染Snackbar,自然找不到目标提示元素。
  • waitFor用法错误:waitFor的作用是轮询等待断言成立,不能把fireEvent、userEvent这类触发交互的副作用逻辑放在回调里,会导致重复触发操作、时序错乱。
修复方案

根据测试目标二选一即可:

方案1:单元测试(验证组件触发Snackbar的逻辑)

如果只需要验证CompanySettings保存操作后正确触发Snackbar提示,不需要真实渲染Snackbar,直接断言Context调用即可:

  1. 把mock的setSnackbar替换为Jest可追踪的mock函数
  2. 把交互逻辑从waitFor中挪出,仅保留异步断言在waitFor内
    参考代码:
const mockSetSnackbar = jest.fn();
const snackbarContextValue = {
  snackbar: {
    text: '',
    severity: 'success',
    open: false,
    autoHideDuration: 3000,
    handleUndo: () => {},
    onClose: () => {},
  },
  setSnackbar: mockSetSnackbar,
};

const component = (props) => {
  return render(<ThemeWrapper storeIncluded={true}>
    <SnackbarContext.Provider value={snackbarContextValue}>
        <CompanySettings />
    </SnackbarContext.Provider>
  </ThemeWrapper>);
};

test('should trigger success snackbar after save', async () => {
  const {getByText, getByTestId, container, queryAllByTestId} = component();
  expect(getByText('Company logo')).toBeInTheDocument();
  expect(getByTestId('custom-icon-Package')).toBeVisible();
  expect(queryAllByTestId('custom-skeleton').length).toBe(8);

  // 等骨架屏加载完成
  await waitFor(() => {
    expect(queryAllByTestId('custom-skeleton').length).toBe(4);
  });

  // 交互逻辑放在waitFor外
  const editBtn = getByText(/Edit company profile/i);
  fireEvent.click(editBtn);
  const inputField = container.querySelector(`input[name=companyName]`);
  userEvent.type(inputField, ' 1');
  fireEvent.click(getByTestId('saveBtn'));

  // 等保存完成
  await waitFor(() => {
    expect(getByText('Gomez Inc 1')).toBeInTheDocument();
    expect(container.querySelector(`input[name=companyName]`)).not.toBeInTheDocument();
  });

  // 断言setSnackbar被正确调用
  expect(mockSetSnackbar).toHaveBeenCalledWith(expect.objectContaining({
    open: true,
    text: `Changes saved to 'company profile'.`,
    severity: 'success'
  }));
});

方案2:集成测试(验证Snackbar真实展示效果)

如果需要验证Snackbar真实出现在页面上,不要自己mock Context,按以下方式调整:

  1. 把Snackbar相关的状态、关闭逻辑、渲染逻辑抽离成独立的SnackbarProvider组件,替代现在散落在Home里的逻辑,所有页面用这个Provider包裹
    参考抽离代码:
// context/SnackbarProvider.jsx
import React, { useState } from 'react';
import Snackbar from 'components/CustomSnackbar/CustomSnackbar';

const SnackbarContext = React.createContext({});

export const SnackbarProvider = ({ children }) => {
  const [snackbar, setSnackbar] = useState({
    open: false,
    text: '',
    severity: 'success',
    autoHideDuration: 3000,
  });

  const handleSnackbarClose = (reason, onClose) => {
    if (reason === 'clickaway') return;
    onClose?.();
    setSnackbar(prev => ({ ...prev, open: false }));
  };

  return (
    <SnackbarContext.Provider value={{ snackbar, setSnackbar }}>
      {children}
      <Snackbar
        autoHideDuration={snackbar.autoHideDuration}
        handleClose={(event, reason) => handleSnackbarClose(reason, snackbar.onClose)}
        handleExternalLink={snackbar.onExternalLink}
        handleUndo={snackbar.handleUndo}
        open={snackbar.open}
        severity={snackbar.severity}
        text={snackbar.text}
      />
    </SnackbarContext.Provider>
  );
};

export default SnackbarContext;
  1. 测试时直接使用这个真实的SnackbarProvider包裹组件,不要自定义mock的context value,这样组件调用setSnackbar时会触发真实的状态更新,Snackbar会被正常渲染,就能断言到提示文案了。
额外避坑点
  • 不要在waitFor回调里写交互触发逻辑,waitFor会在超时窗口内反复执行回调,会导致多次点击、输入,引发不可预期的行为
  • 给waitFor加timeout解决不了问题,你的场景不是等待时间不够,是Snackbar根本没有被挂载到DOM树里,等再久也不会出现
  • 单独mock Context的value时,只有消费Context渲染UI的组件也被一同渲染,对应的UI才会出现,否则Context值更新不会有任何视觉变化

内容的提问来源于stack exchange,提问作者Farrukh Ayaz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 16:21:32