Webpack v4升级v5后Karma测试重复执行,如何保留外部依赖?
问题背景
将Webpack从v4升级到v5后,Karma测试会重复执行4次(v4版本中仅执行1次)。经排查,问题根源在于Karma配置的files数组——保留前5个included: true的全局依赖文件时,测试重复执行;移除这些文件后测试仅执行1次,但这些依赖是PHP SSR应用的必需项,测试无法脱离它们运行。
关键配置冲突
原Karma的files配置中,前5个全局依赖文件均设置included: true,同时测试入口文件modern.testContext.js也设置了included: true:
config.set({ files: [ { pattern: "public/dist/js/global/knockout-bundle.min.js", watched: false, included: true, }, { pattern: "node_modules/jquery/dist/jquery.min.js", watched: false, included: true, }, { pattern: "public/dist/js/global/portal.min.js", watched: false, included: true, }, { pattern: "public/dist/js/global/foundation.min.js", watched: false, included: true, }, { pattern: "public/dist/js/models/user.min.js", watched: false, included: true, }, { pattern: "js/tests/unit/modern.testContext.js", watched: false, included: true, }, // 图片资源配置... ], })
同时Webpack测试配置已声明externals:
externals: { moment: "moment", jquery: "jQuery", knockout: "ko", },
解决方案
问题核心是多个included: true的JS文件会被Karma依次注入测试页面,每个文件加载完成后都可能触发测试初始化,导致重复执行。结合Webpack的externals配置,可通过以下方式解决:
方案1:调整依赖加载方式,统一由测试入口控制
修改Karma的files配置,将前5个依赖的included改为false,保留served: true(确保文件可被访问):
files: [ { pattern: "public/dist/js/global/knockout-bundle.min.js", watched: false, included: false, served: true, }, { pattern: "node_modules/jquery/dist/jquery.min.js", watched: false, included: false, served: true, }, { pattern: "public/dist/js/global/portal.min.js", watched: false, included: false, served: true, }, { pattern: "public/dist/js/global/foundation.min.js", watched: false, included: false, served: true, }, { pattern: "public/dist/js/models/user.min.js", watched: false, included: false, served: true, }, { pattern: "js/tests/unit/modern.testContext.js", watched: false, included: true, }, // 图片资源配置... ],
在测试入口文件modern.testContext.js顶部,按依赖顺序动态加载这些文件:
// 动态加载脚本工具函数 const loadScript = (src) => { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = `/base/${src}`; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); }; // 按依赖顺序加载全局文件 (async () => { await loadScript('node_modules/jquery/dist/jquery.min.js'); await loadScript('public/dist/js/global/knockout-bundle.min.js'); await loadScript('public/dist/js/global/portal.min.js'); await loadScript('public/dist/js/global/foundation.min.js'); await loadScript('public/dist/js/models/user.min.js'); // 原有测试导入逻辑 const importAll = (r) => { r.keys().forEach(r); }; importAll(require.context("./modern/features", true, /\.(spec|test)\.js$/)); importAll(require.context("./modern/main", true, /\.(spec|test)\.js$/)); })();
方案2:合并全局依赖为单个bundle
将5个全局依赖合并为一个单独的vendor-bundle.js(可通过Webpack或其他打包工具实现),在Karma的files中仅注入这个合并后的bundle(included: true),测试入口文件仍保留included: true。合并后的vendor文件不会触发测试执行,仅测试入口会触发一次测试初始化。
方案3:使用Karma插件管理npm依赖
对于jquery、knockout这类npm包,可使用对应Karma插件(如karma-jquery、karma-knockout),将其加入Karma的frameworks数组,替代手动在files中配置:
// 在karma.default.conf.js中修改frameworks frameworks: ["mocha", "chai", "sinon", "webpack", "jquery", "knockout"],
自定义的portal.min.js等文件仍可采用方案1的方式处理。
原理说明
Webpack v5与Karma的集成逻辑发生了变化:当多个included: true的JS文件被注入时,每个文件的加载都会触发测试初始化流程,导致重复执行。而Webpack v4中不存在此触发逻辑,因此仅执行一次。通过将依赖设为served: true但included: false,我们让依赖仅被服务器托管,而非自动注入页面,由测试入口统一控制加载时机,确保仅触发一次测试执行。
内容的提问来源于stack exchange,提问作者Ahmed G

