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

新增组件后React-Jest测试失败,如何Mock带依赖的useEffect?

问题:新增组件后Jest验证错误提示的测试失败

在页面新增组件后,用于验证错误发生时显示错误提示的Jest测试执行失败。代码在本地环境运行正常,但提交阶段测试失败。

错误信息

TestingLibraryElementError: 无法找到包含文本“Student is unable to perform register/unregister activities.”的元素。这可能是因为文本被多个元素拆分。这种情况下,你可以为文本匹配器提供一个函数,让匹配器更灵活。

测试代码

jest.mock('react-query', () => ({
  ...jest.requireActual('react-query'),
  useMutation: jest.fn((_key, cb) => {
    cb();
    return { data: null };
  })
}));

const useMutation = useMutationHook as ReturnType<typeof jest.fn>;

describe('StatusAlert', () => {
  beforeEach(() => {
    useMutation.mockReturnValue({});
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  it('should show error', () => {
    useMutation.mockReturnValueOnce({
      isError: true
    });

    const { getByText } = render(
      <StudentRegister
        students={[studentStub, studentStub]}
        onSuccess={jest.fn()}
      />
    );
    expect(getByText(ErrorDict.ErrorRequest)).toBeInTheDocument();
  });

新增的相关组件代码

interface Props {
  selectedStudents: Array<Student>;
  onSuccessCallback: () => void;
}

export const StudentSelectionBar: FC<Props> = ({
  selectedStudents,
  onSuccessCallback
}) => {
  const [isOpenDropCourseModal, setisOpenDropCourseModal] = useState(false);
  const [studentIds, setStudentIds] = useState<string[]>([]);
  useEffect(() => {
    setStudentIds(selectedStudents.map((student) => student.id));
  }, [selectedStudents]);

  const onToggleOpenDropCourseModal = useCallback(() => {
    setisOpenDropCourseModal(
      (state) => !state
    );
  }, []);

  const {
    isError: isDropCourseError,
    isSuccess: isDropCourseSuccess,
    isLoading: isDropCourseLoading,
    mutateAsync: DropCourseMutation,
    error: DropCourseError
  } = useMutation<void, ApiError>(
    () => dropCourse(selectedStudents.map((student) => student.id)),
    {
      onSuccess() {
        onToggleOpenDropCourseModal();
        onSuccess();
      }
    }
  );

  return (
    <>
      <StatusAlert
        isError={isDropCourseError}
        isSuccess={isDropCourseSuccess}
        errorMessage={
          dropCourseError?.errorMessage || ErrorMessages.FailedPostRequest
        }
        successMessage="Students successfully dropped from course"
      />
      <StatusAlert
        isError={registerMutation.isError}
        isSuccess={registerMutation.isSuccess}
        errorMessage={
          registerMutation.error?.errorMessage || ErrorDict.ErrorRequest
        }
        successMessage="Students successfully registered"
      />
      <StatusAlert
        isError={isError}
        isSuccess={isSuccess}
        errorMessage={
          error?.errorMessage || ErrorDict.ErrorRequest
        }
        successMessage="Students successfully unregistered"
      />
      <Permissions scope={[DropCourseUsers]}>
        <LoadingButton
          color="error"
          variant="contained"
          onClick={onToggleDropCourseUserModal}
          className={styles['action-button']}
          loading={isDropCourseLoading}
          loadingPosition="center"
          disabled={registerMutation.isLoading || isLoading}
        >         
          drop Course
        </LoadingButton>
      </Permissions>
      <DropCourseModal
        isOpen={isOpenDropCourseModal}
        onCloseModal={onToggleOpenDropCourseModal}
        onArchiveUsers={DropCourseMutation}
        users={studentIds}
      />
    </>
  );
};

补充说明

移除组件中的以下useEffect钩子后,测试可正常执行。该钩子用于在列表选中项变化时更新存储studentIds的状态变量。请问是否有办法在测试中Mock这个带依赖的useEffect?

const [studentIds, setStudentIds] = useState<string[]>([]);
useEffect(() => {
  setStudentIds(selectedStudents.map((student) => student.id));
}, [selectedStudents]);

解决方案

  1. 优化文本匹配方式
    按照错误提示,改用函数式匹配器替代精确文本匹配,适配文本被拆分的场景:

    expect(getByText((content, element) => 
      content.includes(ErrorDict.ErrorRequest)
    )).toBeInTheDocument();
    
  2. 等待组件异步更新完成
    useEffect触发的状态更新会导致组件重新渲染,测试可能在错误提示渲染前就执行了断言。用waitFor等待渲染完成:

    it('should show error', async () => {
      useMutation.mockReturnValueOnce({
        isError: true
      });
    
      const { getByText } = render(
        <StudentRegister
          students={[studentStub, studentStub]}
          onSuccess={jest.fn()}
        />
      );
      
      await waitFor(() => {
        expect(getByText(ErrorDict.ErrorRequest)).toBeInTheDocument();
      });
    });
    
  3. Mock useState跳过useEffect依赖
    不需要Mock useEffect,直接MockuseState让studentIds返回预设的初始值,跳过useEffect的执行逻辑:

    jest.mock('react', () => ({
      ...jest.requireActual('react'),
      useState: jest.fn((initial) => {
        // 针对studentIds的状态返回预设值
        if (Array.isArray(initial) && initial.length === 0) {
          return [[studentStub.id, studentStub.id], jest.fn()];
        }
        return [initial, jest.fn()];
      })
    }));
    
  4. 简化组件逻辑(可选)
    既然studentIds只是selectedStudents的id映射,完全可以直接在需要的地方计算,去掉useEffect和状态变量,从根源避免测试问题:

    // 移除useState和useEffect,直接在使用时计算
    const studentIds = selectedStudents.map(student => student.id);
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 10:35:29