我对使用window.location.href的函数进行了一些单元测试-不理想,我宁愿将其传递给它,但在实现中是不可能的。我只是想知道是否有可能在不实际导致测试运行器页面实际转到URL的情况下模拟该值。
window.location.href = "http://www.website.com?varName=foo";
expect(actions.paramToVar(test_Data)).toEqual("bar");
我将茉莉花用于我的单元测试框架。
Answers:
您需要模拟本地上下文并创建自己的window
和window.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
首先从上下文对象中查找所有变量(包括!),如果不存在,则从实际上下文中查找。
'with' statements are not allowed in strict mode.
最好的方法是在某个地方创建一个辅助函数,然后对其进行模拟:
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");
我将提出两个解决方案,这些解决方案在前面的文章中已经提到过:
在访问周围创建一个函数,在生产代码中使用该函数,然后在测试中将其与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");
使用依赖项注入并在测试中注入伪造品:
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");
.andReturn()
应该用代替.and.returnValue()
。
有时您可能拥有一个修改window.location的库,并且希望允许它正常运行但也经过测试。在这种情况下,您可以使用闭包将所需的引用传递给此类库。
/* in mylib.js */
(function(view){
view.location.href = "foo";
}(self || window));
然后在测试中,在包含库之前,您可以在全局范围内重新定义self,并且该库将使用模拟self作为视图。
var self = {
location: { href: location.href }
};
在您的库中,您还可以执行以下操作,因此您可以在测试的任何时候重新定义自我:
/* in mylib.js */
var mylib = (function(href) {
function go ( href ) {
var view = self || window;
view.location.href = href;
}
return {go: go}
}());
在大多数(如果不是全部)现代浏览器中,默认情况下self已经是对window的引用。在实现Worker API的平台中,Worker自身是对全局范围的引用。在node.js中,self和window均未定义,因此,如果您也可以这样做:
self || window || global
如果node.js确实实现了Worker API,则可能会更改。
下面是我模拟window.location.href和/或其他任何可能在全局对象上的方法。
首先,不是直接访问它,而是将其封装在模块中,该模块使用getter和setter保留对象。以下是我的示例。我正在使用require,但这在这里不是必需的。
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。
这对我有用:
delete window.location;
window.location = Object.create(window);
window.location.href = 'my-url';
cannot delete property location
spyOn(component, "getLocationPathname").and.returnValue("/someroute");
使用a// @ts-ignore
对其进行监视,以忽略私有。
IMO,此解决方案是cburgmer的一个小改进,它允许您在源代码中将window.location.href替换为$ window.location.href。当然,我使用的是Karma而不是Jasmine,但我相信这种方法都可以使用。而且我添加了对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语法可追溯到IE 9,并且根据https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set受到广泛支持
这是我的通用解决方案,需要在生产代码中进行额外的导入,但不需要依赖项注入或编写单个包装函数,例如 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');
});
});
您需要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