Webpack 4模块能否配置支持Jasmine监视其成员?遇spyOn报错求助
我之前也碰到过一模一样的坑!Webpack 4对ES模块的处理逻辑和旧版本差异很大——它会把import * as mod导入的模块转成只读的ES模块命名空间对象,而Jasmine的spyOn默认需要修改对象属性来替换方法,自然就会抛出那个"没有setter或不可写"的错误。
问题根源拆解
当你用import * as mod from 'my/module'导入模块时,Webpack 4严格遵循ES模块规范:命名空间对象里的所有导出成员都是只读的,和普通JS对象的可写属性完全不同。Jasmine的常规spyOn要替换这个属性,相当于尝试修改一个只读值,肯定会报错。
可行的解决方案
方案1:调整模块导出方式(适合小范围修改)
把原来的具名导出改成默认导出一个可写对象:
// 原模块代码 export function getField() { /* ... */ } // 修改后 export default { getField: function() { /* ... */ } }
然后导入后直接spy:
import mod from 'my/module'; const funcSpy = spyOn(mod, 'getField'); // 现在能正常工作了
缺点是需要修改模块结构,项目大的话成本较高。
方案2:用spyOnProperty强制开启可写权限
不想改模块的话,可以直接用Jasmine的spyOnProperty,明确标记属性为可写:
import * as mod from 'my/module'; const funcSpy = spyOnProperty(mod, 'getField', 'writable').and.callFake(() => { // 这里写你的间谍逻辑 });
这个方法直接绕过ES模块的只读限制,不用动原模块代码。
方案3:Webpack+Babel转CommonJS(推荐大范围项目)
在Webpack配置里给测试文件单独加规则,用Babel把ES模块转成CommonJS:
module.exports = { module: { rules: [ { test: /\.spec\.js$/, // 匹配你的测试文件后缀 use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { modules: 'commonjs' }] ] } } } ] } }
转成CommonJS后,import * as mod拿到的就是普通的可写module.exports对象,spyOn就能正常使用了。你之前调module.rules[].type没效果,是因为Webpack 4里type: 'commonjs'的优先级不如Babel的转换配置。
方案4:用模块模拟替代直接spy
如果你的测试环境支持类似Jest的模拟语法,可以直接在测试顶部模拟整个模块:
jest.mock('my/module', () => ({ getField: jest.fn() })); import * as mod from 'my/module'; // 直接用mod.getField,它已经是间谍函数了
需要配合babel-plugin-jest-auto-mock之类的插件,适合本来就用Jest生态的项目。
内容的提问来源于stack exchange,提问作者Ryan

