Jasmine单元测试报错:预期spy函数被调用但未调用的问题排查
问题:Jasmine spyOn未捕获函数调用的原因及解决方法
问题场景
我定义了formatViews和test两个函数,test内部直接调用formatViews(10000000)。使用Jasmine+Karma编写单元测试时,尝试通过spyOn(window, 'formatViews')监听该函数,调用test后断言spy被传入10000000调用,但测试失败,spy调用记录为空。
原函数代码
const formatViews = (views) => { if(views >= 1000000) return `${(views / 1000000).toFixed(1)}M`; if(views >= 1000) return `${(views / 1000).toFixed(1)}K`; return views.toString(); }; const test = () => { console.log(formatViews(10000000)); }
测试代码
describe('test', () => { beforeAll(() => { window.formatViews = formatViews; }); it('should test', () => { const formatViewsSpy = spyOn(window, 'formatViews').and.callThrough(); test(); console.log(formatViewsSpy.calls.all()); expect(formatViewsSpy).toHaveBeenCalledWith(10000000); }) });
失败信息
LOG: [] test should test FAILED Expected spy formatViews to have been called with: [ 10000000 ] but it was never called.
Jasmine配置
{ "spec_dir": "spec", "spec_files": [ "**/*[sS]pec.?(m)js" ], "helpers": [ "*.?(m)js" ], "env": { "stopSpecOnExpectationFailure": false, "random": true } }
原因分析
问题核心在于函数作用域和引用的独立性:
test函数内部调用的是当前作用域内的formatViews变量(用const声明的局部函数);- 你虽然把
formatViews赋值给了window.formatViews,但这只是创建了一个新的引用,两个变量指向同一个函数,但test依然引用的是原始的局部变量,而非window上的版本; - 因此,
spyOn监听的window上的函数从未被调用,自然捕获不到调用记录。
解决方案
方案1:修改test函数,调用window上的formatViews
调整原函数代码,让test明确调用挂载到window的版本:
const formatViews = (views) => { if(views >= 1000000) return `${(views / 1000000).toFixed(1)}M`; if(views >= 1000) return `${(views / 1000).toFixed(1)}K`; return views.toString(); }; // 将函数挂载到window window.formatViews = formatViews; const test = () => { // 调用window上的函数版本 console.log(window.formatViews(10000000)); }
方案2:将函数封装到对象中,监听对象属性
把相关函数放到一个公共对象里,让test调用对象的方法,这样spy可以直接监听对象的属性:
// 封装到utils对象 const utils = { formatViews: (views) => { if(views >= 1000000) return `${(views / 1000000).toFixed(1)}M`; if(views >= 1000) return `${(views / 1000).toFixed(1)}K`; return views.toString(); } }; const test = () => { console.log(utils.formatViews(10000000)); }
对应的测试代码:
describe('test', () => { it('should test', () => { const formatViewsSpy = spyOn(utils, 'formatViews').and.callThrough(); test(); expect(formatViewsSpy).toHaveBeenCalledWith(10000000); }) });
方案3:改用function声明顶层函数(自动挂载到window)
如果原代码允许调整声明方式,用function声明顶层函数,它会自动挂载到window对象,test调用的就是window上的版本:
// 用function声明,自动挂载到window function formatViews(views) { if(views >= 1000000) return `${(views / 1000000).toFixed(1)}M`; if(views >= 1000) return `${(views / 1000).toFixed(1)}K`; return views.toString(); }; const test = () => { console.log(formatViews(10000000)); }
此时原测试代码无需修改,spyOn(window, 'formatViews')就能正常捕获到调用。
内容的提问来源于stack exchange,提问作者D Pranav
相关产品推荐
相关产品推荐

