【问题标题】:Is it possible to mock the window.location object for a qUnit test?是否可以模拟 qUnit 测试的 window.location 对象?
【发布时间】:2012-08-07 03:44:28
【问题描述】:

假设我有一个实用函数,为了简单起见(实际情况复杂且无关紧要),它返回当前窗口的查询字符串。

var someUtilityFunction = () {
    return window.location.search.substring(1);
};

现在我想在 qUnit 中对这个函数进行单元测试(不确定测试工具是否相关):

test('#1 someUtilityFunction works', function () {
    // setup
    var oldQS = window.location.search;
    window.location.search = '?key1=value1&key2=value2&key3=value3';

    var expectedOutput = 'key1=value1&key2=value2&key3=value3';

    // test
    equals(someUtilityFunction(),
        expectedOutput,
        'someUtilityFunction works as expected.');

    // teardown
    window.location.search = oldQS;
});

这里的问题是,将window.location.search 设置为不同的查询字符串会导致页面重新加载,实际上进入了无限请求循环。有什么方法可以模拟出 window.location 对象而不someUtilityFunction 函数进行任何更改?

【问题讨论】:

    标签: javascript unit-testing query-string qunit


    【解决方案1】:

    几天前我们遇到了同样的问题。主要有两种做法:

    重写你的代码

    这可能不是最好的(如果有的话)解决方案,但请考虑将 window 对象传递给您的函数以使模拟更容易。更好的是,使用闭包并封装您的代码。这还有几个优点:

    • 您可以隐藏全局变量
    • 您可以使用私有本地变量
    • 您可以避免命名冲突
    • 阴影使模拟变得非常容易,只需传入其他内容

    包装您的代码

    您可以将所有代码包装在一个函数中,该函数将窗口对象模拟为一个局部变量。你基本上也有两种可能性:

    假设这是模拟:

    var customWindow = {
        location: {
            search: "",
            hash: ""
        }
    };
    

    使用闭包

    var someUtilityFunction;
    
    (function(window) {
        // window is now shadowed by your local variable
        someUtilityFunction = () {
            return window.location.search.substring(1);
        };
    })(customWindow);
    

    这会用本地window 遮蔽全局window

    使用with 语句

    虽然我通常强烈反对,但它确实可以解决很多问题。由于它基本上重新映射了您的范围,因此您可以非常轻松地模拟您的环境。

    // first some more preparation for our mock
    customWindow.window = customWindow;
    
    with(customWindow) {
    
        // this still creates the var in the global scope
        var someUtilityFunction = () {
            // window is a property of customWindow
            return window.location.search.substring(1);
        };
    
        // since customWindow is our scope now
        // this will work also
        someUtilityFunction = () {
            // location is a property of customWindow too
            return location.search.substring(1);
        };
    
    }
    

    顺便说一句:我不知道search 属性是否与hash 属性有相同的症状——即有时包括问号,有时不包括。但您可能需要考虑使用

    window.location.search.replace(/^\?/, "");
    

    而不是

    window.location.substr(1);
    

    【讨论】:

    • 感谢您的回答。重写代码无济于事,因为它可以选择使用window 对象-这种情况是在未通过时进行测试。将函数包装在“模拟块”中将更改函数中的代码,看起来我可能必须这样做。
    • 您需要以某种方式包装您的代码以避免重写您的代码。我刚刚尝试了一个 with 包装器,它也很好用(但是使用它感觉不对)。然后,您可以使您的自定义 window 对象全局可用以模拟您的属性,并使其在您的代码和测试中可用。我想这也是 jQuery 和 Zepto 的做法,因为它们也将 windowdocument 传递给它们的包装器。
    【解决方案2】:

    我使用window.history.pushState 取得了一些成功。见this StackOverflow answer。对于每个单元测试,我调用一个函数setQueryString('var=something'),然后像这样实现:

    function setQueryString(queryString) {
      window.history.pushState({}, '', '?' + queryString);
    }
    

    您需要使用 QUnit.module 的afterEach 方法清除查询字符串,否则您的查询字符串将被设置为最终测试的值,您会得到奇怪的结果。

    【讨论】:

    • 对我不起作用。如果我做一个window.history.pushState(...); 并直接用console.log(window.location) 检查它,它会给我Qunit-html 页面的当前URL。你有示例代码吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-21
    • 2011-11-22
    • 1970-01-01
    相关资源
    最近更新 更多