Javascript中的mocking window.location.href

Javascript中的mocking window.location.href,javascript,mocking,jasmine,Javascript,Mocking,Jasmine,我对一个使用window.location.href的函数进行了一些单元测试——这并不理想,我更希望能够通过测试,但在实现中这是不可能的。我只是想知道是否可以模拟这个值,而不会导致我的测试运行程序页面实际转到URL window.location.href = "http://www.website.com?varName=foo"; expect(actions.paramToVar(test_Data)).toEqual("bar"); 我将jasmine用于我的单元测试

我对一个使用window.location.href的函数进行了一些单元测试——这并不理想,我更希望能够通过测试,但在实现中这是不可能的。我只是想知道是否可以模拟这个值,而不会导致我的测试运行程序页面实际转到URL

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

我将jasmine用于我的单元测试框架。

您需要模拟本地上下文,并创建自己版本的
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/...

如果将
一起使用,则所有变量(包括
窗口
!)将首先从上下文对象中查看,如果不存在,则从实际上下文中查看。

最好的方法是在某处创建一个帮助函数,然后模拟:

 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,库将使用模拟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 self中是对全局范围的引用。在node.js中,未定义self和window,因此如果需要,也可以执行以下操作:

self || window || global

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

我将提出两种解决方案,这两种解决方案在之前的文章中已经提到:

  • 围绕access创建一个函数,在生产代码中使用该函数,并在测试中使用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");
    

    • 下面是我对mock 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
      变量。它最初设置为全局
      窗口
      对象,但未设置为您放入的假窗口对象。因此,现在在您替换它之后,代码将获得您的假窗口对象及其您放置的假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语法可以追溯到IE 9,并且根据

      得到了广泛的支持。您需要在同一页面上伪造
      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()

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

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

      //windowProxy.js
      /*
      *此文件仅作为窗口对象的代理引用存在
      *因此,您可以在单元测试期间模拟窗口对象。
      */
      导出默认窗口;
      
      //prod/someCode.js
      从'path/to/windowProxy.js'导入windowProxy;
      导出函数changeUrl(){
      windowProxy.location.href=https://coolsite.com';
      }
      
      //测试/someCode.spec.js
      从“../prod/someCode.js”导入{changeUrl};
      将*作为windowProxy从“../prod/path/to/windowProxy.js”导入;
      描述('changeUrl',()=>{
      让我们打开窗户;
      在每个之前(()=>{
      mockWindow={};
      windowProxy.default=myMockWindow;
      });
      之后(()=>{
      windowProxy.default=窗口;
      });
      它('更改url',()=>{
      changeUrl();
      expect(mockWindow.location.href).toEqual('https://coolsite.com');
      });
      });
      
      当我想调用with中的函数时,我遇到了同样的问题。如何解决?在Jasmine 2.x中,第一个示例中使用的语法似乎发生了一些变化
      .andReturn()
      应替换为
      。.returnValue()
      严格模式下不允许使用
      'with'语句。
      在我看来,您不能用它编写可读的测试。我认为@Kurt Harrier提出的解决方案要好得多-1WITH(obj){}语句已弃用,因此在严格模式下无效。仅适用于jest,不适用于f