【问题标题】:mocking window.location.href in Javascript在Javascript中模拟window.location.href
【发布时间】:2011-06-15 02:30:41
【问题描述】:

我对一个使用 window.location.href 的函数进行了一些单元测试——不理想我宁愿将它传递给它,但它在实现中是不可能的。我只是想知道是否可以在不实际导致我的测试运行器页面实际转到 URL 的情况下模拟此值。

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

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

【问题讨论】:

  • 当我想在 with 中调用函数时遇到了同样的问题。你是怎么解决的?

标签: javascript mocking jasmine


【解决方案1】:

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

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

在生产中,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');
  });
});

【讨论】:

    【解决方案2】:

    这对我有用:

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

    【讨论】:

    • 仅适用于开玩笑,不适用于业力 (cannot delete property location)
    • 如果使用 TS 你可以试试这个:delete (window).location;
    • 在运行时也不起作用,我最终创建了一个私有方法来返回路径名并使用 spyOn(component, "getLocationPathname").and.returnValue("/someroute");// @ts-ignore 监视它以忽略私有。
    【解决方案3】:

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

     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");
    

    【讨论】:

      【解决方案4】:

      您需要在同一页面上伪造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
      

      【讨论】:

        【解决方案5】:

        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 得到广泛支持

        【讨论】:

          【解决方案6】:

          以下是我用来模拟 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;
          

          最后设置完成,您可以通过将 window 对象替换为您想要的假对象来测试您的代码。

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

          这将更改窗口模块中的win 变量。它最初设置为全局window 对象,但未设置为您放入的假窗口对象。所以现在替换它之后,代码将获取您的假窗口对象及其假href。

          【讨论】:

            【解决方案7】:

            我将提出两个解决方案,这些解决方案在之前的帖子中已经暗示过:

            • 围绕访问创建一个函数,在您的生产代码中使用它,并在您的测试中使用 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");
              
            • 使用dependency injection 并在您的测试中注入一个假的:

              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");
              

            【讨论】:

            • 在 Jasmine 2.x 中,第一个示例中使用的语法似乎发生了一些变化。 .andReturn() 应替换为 .and.returnValue()
            【解决方案8】:

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

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

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

            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 已经是对 window 的引用。在实现 Worker API 的平台中,Worker self 内是对全局范围的引用。在 node.js 中 self 和 window 都没有定义,所以如果你愿意,你也可以这样做:

            self || window || global
            

            如果 node.js 确实实现了 Worker API,这可能会改变。

            【讨论】:

              【解决方案9】:

              您需要模拟本地上下文并创建自己的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!)将首先从上下文对象中查找,如果不存在则从实际上下文中查找。

              【讨论】:

              • 'with' statements are not allowed in strict mode.
              • 在我看来你不能用它编写可读的测试。我认为@Kurt Harriger 提出的解决方案要好得多。 -1
              • with(obj) {} 语句已弃用,因此在严格模式下无效。
              • with 到底是什么?
              • with 已成为过去。该答案已有 10 年历史,即便如此,with 也不再使用了。
              猜你喜欢
              • 2020-12-04
              • 2019-05-29
              • 2013-09-09
              • 2016-07-24
              • 1970-01-01
              • 2011-10-28
              • 2012-04-24
              • 2015-04-24
              • 2014-02-20
              相关资源
              最近更新 更多