新增组件后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]);
解决方案
优化文本匹配方式
按照错误提示,改用函数式匹配器替代精确文本匹配,适配文本被拆分的场景:expect(getByText((content, element) => content.includes(ErrorDict.ErrorRequest) )).toBeInTheDocument();等待组件异步更新完成
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(); }); });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()]; }) }));简化组件逻辑(可选)
既然studentIds只是selectedStudents的id映射,完全可以直接在需要的地方计算,去掉useEffect和状态变量,从根源避免测试问题:// 移除useState和useEffect,直接在使用时计算 const studentIds = selectedStudents.map(student => student.id);
内容的提问来源于stack exchange,提问作者SJaka
相关产品推荐
相关产品推荐

