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

如何用Jasmine模拟window对象上的外部ATS库?(Karma环境)

在Jasmine中模拟window上的外部ATS库(无需修改主代码)

问题背景

我正在开发一款JavaScript SDK,当前用到了挂载在window对象上的外部库ats,核心业务代码如下:

private getFromATS(): string {
  return window.ats.retrieveEnvelope(function (envelope: string) {
    console.log('Located ATS.js');
    return JSON.parse(envelope).envelope;
  });
}

编写单元测试时,尝试了四种模拟方法都没生效:

// Method 1
spyOn(window.ats, 'retrieveEnvelope').and.returnValue(function (
  callback: Callback<String>
) {
  return callback('{"envelope":"asdfasdfasdf"}');
});

// Method 2
spyOn(window, 'ats').and.returnValue({
  retrieveEnvelope: function (callback: Callback<String>) {
    return callback('{"envelope":"asdfasdfasdf"}');
  },
});

// Method 3
window = Object.create(window);
Object.defineProperty(window, 'ats', {
  value: {
    retrieveEnvelope: function (callback: Callback<String>) {
      return callback('{"envelope":"asdfasdfasdf"}');
    },
  },
  writable: true,
});

// Method 4
const windowStud = {
  ats: {
    retrieveEnvelope: function (callback: Callback<String>) {
      return callback('{"envelope":"asdfasdfasdf"}');
    },
  },
} as Window & typeof globalThis;

想知道怎么在Jasmine中正确模拟window上的这个外部属性?另外,虽然依赖注入可行,但能不能不修改主代码就实现模拟?


正确模拟方案(无需改动主代码)

方案1:先初始化window.ats再创建间谍

如果测试环境里window.ats原本不存在,得先给它建个空对象,再对retrieveEnvelope方法做间谍模拟:

beforeEach(() => {
  // 先确保window.ats存在,防止spyOn报错
  window.ats = window.ats || {};
  // 模拟retrieveEnvelope的行为:调用传入的回调,同时匹配原方法的返回值逻辑
  spyOn(window.ats, 'retrieveEnvelope').and.callFake((callback) => {
    const mockEnvelopeStr = '{"envelope":"asdfasdfasdf"}';
    callback(mockEnvelopeStr);
    // 原方法返回的是解析后的envelope,这里同步返回对应值
    return JSON.parse(mockEnvelopeStr).envelope;
  });
});

方案2:直接替换window.ats并在测试后恢复

如果要完全替换整个window.ats,可以在测试前覆盖属性,测试结束后恢复原对象,避免影响其他测试:

let originalAts: typeof window.ats;

beforeEach(() => {
  // 保存原始的window.ats,测试后还原
  originalAts = window.ats;
  // 替换成模拟对象,用jasmine.createSpy方便断言调用情况
  window.ats = {
    retrieveEnvelope: jasmine.createSpy('retrieveEnvelope').and.callFake((callback) => {
      const mockEnvelopeStr = '{"envelope":"asdfasdfasdf"}';
      callback(mockEnvelopeStr);
      return JSON.parse(mockEnvelopeStr).envelope;
    })
  };
});

afterEach(() => {
  // 恢复原始对象,避免测试间污染
  window.ats = originalAts;
});

为什么之前的方法失效?

  • Method 1:如果测试环境中window.ats或window.ats.retrieveEnvelope不存在,spyOn会直接报错——它只能监听已经存在的对象属性。
  • Method 2:window.ats作为全局属性通常是不可配置的,spyOn(window, 'ats')无法替换这个属性,所以模拟无效。
  • Method 3:重新赋值window = Object.create(window)会让测试代码和真实全局window断开联系,SDK代码还是访问原始的window,所以模拟不生效。
  • Method 4:创建了模拟的windowStud但没让SDK代码使用它,SDK依然访问全局window,自然起不到作用。

结论

完全可以不修改主代码实现模拟,关键是要保证模拟的是真实全局window对象上的ats属性,同时在测试前后做好环境的初始化和恢复,避免测试用例之间互相干扰。

内容的提问来源于stack exchange,提问作者Niko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 16:52:48