在 Javascript 中模仿 window.location.href

我对一个使用 window.location.href 的函数进行了一些单元测试——这并不理想,我宁愿将其传入,但在实现中是不可能的。我只是想知道是否有可能在不导致我的测试运行程序页面实际访问 URL 的情况下模仿这个值。

  window.location.href = "http://www.website.com?varName=foo";
expect(actions.paramToVar(test_Data)).toEqual("bar");

我正在使用 Jasmine 作为我的单元测试框架。

115096 次浏览

您需要模拟本地上下文并创建自己版本的 windowwindow.location对象

var localContext = {
"window":{
location:{
href: "http://www.website.com?varName=foo"
}
}
}


// simulated context
with(localContext){
console.log(window.location.href);
// http://www.website.com?varName=foo
}


//actual context
console.log(window.location.href);
// http://www.actual.page.url/...

如果你使用 with,那么所有的变量(包括 window!)将首先从上下文对象看,如果不存在,那么从实际上下文。

最好的方法是在某处创建一个 helper 函数,然后模仿它:

 var mynamespace = mynamespace || {};
mynamespace.util = (function() {
function getWindowLocationHRef() {
return window.location.href;
}
return {
getWindowLocationHRef: getWindowLocationHRef
}
})();

现在,在代码中不直接使用 window.location.href,而是简单地使用以下命令。然后,只要需要返回模拟值,就可以替换此方法:

mynamespace.util.getWindowLocationHRef = function() {
return "http://mockhost/mockingpath"
};

如果您想要窗口位置的特定部分,例如查询字符串参数,那么也为此创建帮助器方法,并将解析保留在主代码之外。一些框架(如 Jasmine)拥有测试间谍,它们不仅可以模仿函数返回所需的值,还可以验证函数是否被调用:

spyOn(mynamespace.util, 'getQueryStringParameterByName').andReturn("desc");
//...
expect(mynamespace.util.getQueryStringParameterByName).toHaveBeenCalledWith("sort");

有时,您可能有一个修改 window.location 的库,您希望允许它正常运行,但也希望对其进行测试。如果是这种情况,可以使用闭包将所需的引用传递给库,如下所示。

/* in mylib.js */
(function(view){
view.location.href = "foo";
}(self || window));

然后在测试中,在包含库之前,可以在全局范围内重新定义 self,库将使用 mock self 作为视图。

var self = {
location: { href: location.href }
};

在你的库中,你也可以做如下事情,这样你就可以在测试的任何时候重新定义 self:

/* in mylib.js */
var mylib = (function(href) {
function go ( href ) {
var view = self || window;
view.location.href = href;
}
return {go: go}
}());

在大多数(如果不是所有的话)现代浏览器中,self 默认情况下已经是对窗口的引用。在实现 Worker API 的平台中,在 Worker 自身中是对全局范围的引用。在 node.js 中,self 和 window 都没有定义,所以如果你想,你也可以这样做:

self || window || global

如果 node.js 真的实现了 Worker API,这种情况可能会改变。

我想提出两个解决办法,这两个办法在以前的职位中已经提到过:

  • 在访问周围创建一个函数,在生产代码中使用它,并在测试中使用 Jasmine 存根:

    var actions = {
    getCurrentURL: function () {
    return window.location.href;
    },
    paramToVar: function (testData) {
    ...
    var url = getCurrentURL();
    ...
    }
    };
    // Test
    var urlSpy = spyOn(actions, "getCurrentURL").andReturn("http://my/fake?param");
    expect(actions.paramToVar(test_Data)).toEqual("bar");
    
  • Use a dependency injection and inject a fake in your test:

    var _actions = function (window) {
    return {
    paramToVar: function (testData) {
    ...
    var url = window.location.href;
    ...
    }
    };
    };
    var actions = _actions(window);
    // Test
    var fakeWindow = {
    location: { href: "http://my/fake?param" }
    };
    var fakeActions = _actions(fakeWindow);
    expect(fakeActions.paramToVar(test_Data)).toEqual("bar");
    

下面是我用来模拟 window.location.href 和/或其他可能在全局对象上使用的方法。

首先,不要直接访问它,而是将它封装在一个模块中,其中对象保存在 getter 和 setter 中。下面是我的例子。我使用的是必要条件,但这里没有必要。

define(["exports"], function(exports){


var win = window;


exports.getWindow = function(){
return win;
};


exports.setWindow = function(x){
win = x;
}


});

现在,通常在代码中执行类似于 window.location.href的操作,现在可以执行以下操作:

var window = global_window.getWindow();
var hrefString = window.location.href;

最后,设置完成,您可以通过使用一个假对象替换窗口对象来测试代码。

fakeWindow = {
location: {
href: "http://google.com?x=y"
}
}
w = require("helpers/global_window");
w.setWindow(fakeWindow);

这将改变窗口模块中的 win变量。它最初设置为全局 window对象,但是没有设置为您放入的假窗口对象。所以现在在你替换它之后,代码会得到你的假窗口对象和它的假 href。

IMO,这个解决方案是 cburgmer 的一个小改进,因为它允许您在源代码中用 $window.location.href 替换 window.location.href。虽然我使用的是卡玛而不是茉莉,但我相信这种方法将与任何一个。我还增加了对 Sinon 的依赖。

首先是服务/单件:

function setHref(theHref) {
window.location.href = theHref;
}
function getHref(theHref) {
return window.location.href;
}


var $$window = {
location: {
setHref: setHref,
getHref: getHref,
get href() {
return this.getHref();
},
set href(v) {
this.setHref(v);
}
}
};
function windowInjectable() {  return $$window; }

现在我可以通过像下面这样注入 windowInjectable ()作为 $window 来在代码中设置 location.href:

function($window) {
$window.location.href = "http://www.website.com?varName=foo";
}

并在单元测试中对其进行模拟,看起来就像:

sinon.stub($window.location, 'setHref');  // this prevents the true window.location.href from being hit.
expect($window.location.setHref.args[0][0]).to.contain('varName=foo');
$window.location.setHref.restore();

Getter/setter 语法可以追溯到 IE9,并且根据 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set广泛支持

你需要在同一页上伪造 window.location.href。 在我的例子中,这个修剪工作很完美:

$window.history.push(null, null, 'http://server/#/YOUR_ROUTE');
$location.$$absUrl = $window.location.href;
$location.replace();


// now, $location.path() will return YOUR_ROUTE even if there's no such route

这对我有用:

delete window.location;
window.location = Object.create(window);
window.location.href = 'my-url';

下面是我的通用解决方案,它需要额外的生产代码导入,但不需要依赖注入或编写单独的包装函数,如 getHref()

基本上,我们将窗口放到一个单独的文件中,然后我们的产品代码间接地从该文件导入窗口。

生产中,windowProxy === window。 在测试中,我们可以改变导出 windowProxy的模块,并用一个新的临时值来模拟它。

// windowProxy.js


/*
* This file exists solely as proxied reference to the window object
* so you can mock the window object during unit tests.
*/
export default window;
// prod/someCode.js
import windowProxy from 'path/to/windowProxy.js';


export function changeUrl() {
windowProxy.location.href = 'https://coolsite.com';
}
// tests/someCode.spec.js
import { changeUrl } from '../prod/someCode.js';
import * as windowProxy from '../prod/path/to/windowProxy.js';


describe('changeUrl', () => {
let mockWindow;
beforeEach(() => {
mockWindow = {};
windowProxy.default = myMockWindow;
});


afterEach(() => {
windowProxy.default = window;
});


it('changes the url', () => {
changeUrl();
expect(mockWindow.location.href).toEqual('https://coolsite.com');
});
});

这与 cimphoff 的建议相似,但是它使用了 Angular 中的依赖注入。我觉得我应该加上这个以防有人来这里寻找角度解。

在模块中,可能 app.module 添加了一个窗口提供程序,如下所示:

@NgModule({
...
providers: [
{
provide: Window,
useValue: window,
},
],
...
})

然后在使用 window 的组件中,在构造函数中注入 window。

constructor(private window: Window)

现在不再直接使用 window,而是在使用 window 时使用组件属性。

this.window.location.href = url

这样,您就可以使用 TestBed 在 Jasmine 测试中设置提供程序。

    beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [
{
provide: Window,
useValue: {location: {href: ''}},
},
],
}).compileComponents();
});