如何用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
相关产品推荐
相关产品推荐

