You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 15:47:06