Jest报错:Cannot read properties of undefined (reading 'createRoot') 求助
TypeError: Cannot read properties of undefined (reading 'createRoot') 问题场景
我的项目基于Vite + TypeScript,使用Jest(搭配React Testing Library)运行测试时触发如下错误:
Test suite failed to run
TypeError: Cannot read properties of undefined (reading 'createRoot')
export const queryClient = new QueryClient(); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( ^ <React.StrictMode> return (<App />)
测试代码如下:
test("it should trigger form errors when description and amount is not filled", async () => { // arrange // render(<AddExpenditure />); render( <div id="root"> <AddExpenditure /> </div> ); screen.debug(); });
测试其他组件时一切正常,只有测试AddExpenditure组件时会触发该错误。
现有配置
Jest配置
/** @type {import('ts-jest').JestConfigWithTsJest} */ export default { preset: "ts-jest", testEnvironment: "jsdom", moduleNameMapper: { "\\.(css)$": "./mocks/styleMock.js", }, };
tsconfig配置
{ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "Node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] }
问题原因
错误根源在项目入口文件(比如main.tsx)的ReactDOM.createRoot调用:
当Jest运行测试时,会加载所有被测试组件依赖的模块。如果AddExpenditure组件(或其依赖)引入了入口文件中导出的queryClient,Jest就会执行整个入口文件的代码。此时测试环境中还没创建id="root"的DOM元素,document.getElementById("root")返回undefined,导致调用createRoot时报错。
其他组件测试正常,是因为它们没有间接引入入口文件导出的全局变量,所以入口文件的渲染逻辑不会被执行。
修复方案
方案1:让入口文件仅在浏览器环境执行渲染
修改入口文件,添加环境判断,只在浏览器环境下执行DOM渲染逻辑:
export const queryClient = new QueryClient(); // 仅在浏览器环境执行渲染 if (typeof window !== 'undefined') { ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( <React.StrictMode> <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider> </React.StrictMode> ); }
方案2:Mock入口文件的渲染逻辑
如果不想修改入口文件,可以在Jest配置中添加模块Mock,跳过入口文件的渲染代码:
在Jest配置的moduleNameMapper中添加:
moduleNameMapper: { "\\.(css)$": "./mocks/styleMock.js", "^./main.tsx$": "<rootDir>/mocks/mainMock.tsx" // 替换为你的入口文件路径 }
然后创建mocks/mainMock.tsx,只导出需要的变量,不执行渲染:
import { QueryClient } from '@tanstack/react-query'; export const queryClient = new QueryClient();
方案3:重构全局依赖,分离入口文件逻辑
把queryClient单独抽离到独立文件(比如src/utils/queryClient.ts),避免在入口文件中导出全局变量:
// src/utils/queryClient.ts import { QueryClient } from '@tanstack/react-query'; export const queryClient = new QueryClient();
之后在入口文件和需要的组件中,都从这个文件导入queryClient。这样入口文件就不会因为导出变量而被测试代码加载执行渲染逻辑。
内容的提问来源于stack exchange,提问作者importTurtle

