React项目Jest测试内部Webpack依赖包时遇publicPath不支持错误
问题:Jest测试环境中使用Webpack构建的内部React包时出现
Automatic publicPath is not supported in this browser错误 问题详情
运行涉及内部依赖包的Jest测试时触发错误,该包由React+Webpack构建。仅Jest环境会出现此问题,Storybook或应用直接运行时一切正常。
报错信息:
Automatic publicPath is not supported in this browser
该内部包用于提供组件和主题配置,推测是Jest使用的jsdom环境未正确处理Webpack的publicPath导致此错误。
项目配置
jest.config.ts
module.exports = { collectCoverageFrom: [ 'src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts', '!src/index.tsx', '!src/constants/**/*', ], coverageDirectory: 'output/coverage', coveragePathIgnorePatterns: [ './src/*/*.types.{ts,tsx}', './src/index.tsx', './src/redux', ], coverageReporters: ['clover'], testEnvironment: 'jest-environment-jsdom', setupFilesAfterEnv: [ '<rootDir>/src/config/setupTests.ts', ], transform: { '^.+\\.(js|jsx|ts|tsx)$': [ '@swc/jest', { jsc: { transform: { react: { runtime: 'automatic', }, }, }, }, ], '\\.css$': 'jest-css-modules-transform', }, transformIgnorePatterns: [ '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', '^.+\\.module\\.(css|sass|scss)$', ], moduleDirectories: ['node_modules', __dirname], moduleNameMapper: { '\\.(css|scss)$': 'identity-obj-proxy', }, snapshotFormat: { printBasicPrototype: true, }, reporters: [ 'default', ['jest-junit', { outputDirectory: 'output', outputName: 'junit.xml' }], ], };
package.json
{ "name": "project", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite --host", "build": "tsc && vite build", "lint": "npm run lint:js; js_status=$?; npm run lint:css; css_status=$?; [ $js_status -ne 0 ] || [ $css_status -ne 0 ] && exit 1 || exit 0", "lint:js": "npx eslint src/**/*.{ts,tsx}", "lint:js:fix": "npx eslint src/**/*.{ts,tsx} --fix", "lint:css": "npx stylelint src/**/*.{css,scss} --aei", "lint:css:fix": "npx stylelint src/**/*.{css,scss} --fix", "test": "jest --verbose", "test:coverage": "jest --coverage", "test:coverage:html": "jest --coverage --coverageReporters=html", "storybook": "storybook dev -p 6006", "storybook:build": "storybook build -o dist/storybook" }, "dependencies": { "@babel/core": "^7.22.9", "@babel/preset-env": "^7.22.9", "@babel/preset-react": "^7.22.5", "@this-is-the-package-but-its-internal": "1.2.5", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.1", "@mui/material": "^5.14.2", "@mui/utils": "^5.15.14", "@reduxjs/toolkit": "^1.9.5", "@storybook/addon-a11y": "^8.3.6", "@storybook/addon-actions": "^8.3.6", "@storybook/addon-essentials": "^8.3.6", "@storybook/addon-interactions": "^8.3.6", "@storybook/addon-links": "^8.3.6", "@storybook/blocks": "^8.3.6", "@storybook/react": "^8.3.6", "@storybook/react-vite": "^8.3.6", "@storybook/test": "^8.3.6", "@swc/jest": "^0.2.27", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", "@types/jest": "^29.5.3", "@types/pendo-io-browser": "^2.17.9", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@types/react-redux": "^7.1.25", "@typescript-eslint/eslint-plugin": "^5.37.0", "@typescript-eslint/parser": "^5.37.0", "@vitejs/plugin-react": "^4.0.3", "axios": "^1.6.7", "babel-jest": "^29.6.2", "classnames": "^2.3.2", "eslint": "^8.55.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-react-app": "^7.0.1", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-react": "^7.31.8", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.10.1", "i18next": "^23.3.0", "i18next-browser-languagedetector": "^7.1.0", "i18next-http-backend": "^2.2.1", "identity-obj-proxy": "^3.0.0", "jest": "^29.6.1", "jest-css-modules-transform": "^4.4.2", "jest-environment-jsdom": "^29.6.2", "jest-junit": "^16.0.0", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "msw": "^1.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.51.2", "react-i18next": "^13.5.0", "react-redux": "^8.1.1", "react-router-dom": "^6.14.2", "redux": "^4.2.1", "redux-persist": "^6.0.0", "redux-thunk": "^2.4.2", "sass": "^1.64.1", "storybook": "^8.3.6", "storybook-react-i18next": "^3.1.7", "stylelint": "^14.11.0", "stylelint-config-standard-scss": "^5.0.0", "ts-node": "^10.9.1", "typescript": "5.1.6", "vite": "^4.4.5" }, "jest-junit": { "output": "output/coverage/junit/junit.xml", "usePathForSuiteName": "true" }, "devDependencies": { "@types/js-cookie": "^3.0.6" } }
环境信息
- Node: v20.18.0
- npm: 10.8.2
解决方案
方案1:在Jest全局环境中模拟publicPath
在setupTests.ts中添加以下代码,为jsdom环境注入Webpack所需的publicPath相关全局变量:
// 模拟Webpack的publicPath global.__webpack_public_path__ = '/';
方案2:修改Jest的transformIgnorePatterns,让内部包被转译
由于内部包是Webpack构建的,可能包含未转译的代码导致jsdom不兼容。修改jest.config.ts中的transformIgnorePatterns,排除内部包:
transformIgnorePatterns: [ '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', '^.+\\.module\\.(css|sass|scss)$', // 排除内部包,让它被@swc/jest转译 '!node_modules/@this-is-the-package-but-its-internal/', ],
方案3:在内部包的Webpack配置中显式设置publicPath
如果有权限修改内部包的Webpack配置,添加publicPath配置,避免自动检测:
// 内部包的webpack.config.js module.exports = { output: { publicPath: '/', // 或者根据实际部署路径设置 }, };
方案4:使用moduleNameMock模拟内部包
如果测试不需要内部包的真实逻辑,可以直接模拟整个包:
在jest.config.ts的moduleNameMapper中添加:
moduleNameMapper: { '\\.(css|scss)$': 'identity-obj-proxy', '@this-is-the-package-but-its-internal': '<rootDir>/__mocks__/internal-package.ts', },
然后创建__mocks__/internal-package.ts,导出模拟的组件和配置:
// 模拟内部包的核心导出 export const Button = () => null; export const themeConfig = {}; // 其他组件/配置按需模拟
内容的提问来源于stack exchange,提问作者PedroB
相关产品推荐
相关产品推荐

