用Sinon模拟window.location.href

32

我正在尝试测试一些客户端代码,为此我需要使用Mocha/Sinon模拟window.location.href属性的值。

迄今为止我尝试过以下方法 (使用此示例):

describe('Logger', () => {
    it('should compose a Log', () => {
        var stub = sinon.stub(window.location, 'href', 'http://www.foo.com');
    });
});

跑步者显示以下错误:

类型错误:自定义存根应该是一个函数或属性描述符

将测试代码更改为:

describe('Logger', () => {
    it('should compose a Log', () => {
        var stub = sinon.stub(window.location, 'href', {
            value: 'foo'
        });
    });
});

出现了以下错误:

TypeError: 尝试将字符串属性 href 包装为函数

将函数作为第三个参数传递给 sinon.stub 也不起作用。

有没有办法提供一个假的 window.location.href 字符串,同时避免重定向(因为我是在浏览器中进行测试)?

3个回答

22

beforeEachit 中使用 global 来模拟 window 对象进行测试。

例如:

it('should compose a Log', () => {
   global.window = {
       location: {
           href: {
               value: 'foo'
           }
       }
   }
  //.... call the funciton 
});

1
我喜欢这个解决方案,因为它足够简单,通常可以完成工作。其他选项适用于更复杂的要求。 - Andrew Smith
1
我也是,我非常高兴你觉得它有用。 - KhaledMohamedP
7
如果我理解正确的话,覆盖全局对象将会影响到其余的测试文件,对吗?这意味着它会被其他测试用例所覆盖。这可能不是问题,但需要注意。 - perry
4
为了解决这个问题,我在测试开始时保存了全局对象const originalWindow = global.window,然后在测试结束时恢复它 global.window = originalWindow - Daniel Elkington
15
不确定那对你们是如何起作用的,因为我收到了一个错误提示,说无法给一个只读属性赋值。 - Arvin
显示剩余3条评论

14
使用 window.location.assign(url) 代替覆盖 window.location 的值。这样,您就可以轻松地对 window.location 对象上的 assign 方法进行桩测试。 http://www.w3schools.com/jsref/met_loc_assign.asp 更新:我在一个无头浏览器中测试了这个方法,但如果您在 Chrome 中运行测试,则可能无法正常工作。请参阅 @try-catch-finally 的回复

这个解释不够清楚。有没有人能够编辑一下并提供一个真正的解释,说明这意味着什么以及如何做到这一点? - mawburn
1
在我看来,这个解释已经足够完美地涉及到了讨论的问题。为了详细说明他的回答,请将客户端代码更改为使用 window.location.assign(url) 替换 window.location = url。然后,您可以像这样存根 location 对象的 assign 方法:var stub = sinon.stub(window.location, 'assign') - oligofren
5
这个答案是错误的。你不能重新分配(stub)location.assign,至少在Chrome浏览器中,window.location.assign = function() { console.log("meh"); }没有效果。当调用location.assign()时,会出现Uncaught TypeError: Failed to execute 'assign' on 'Location': 1 argument required, but only 0 present.(表示原生方法会抱怨缺少参数)。 - try-catch-finally
1
谢谢您的纠正。我只是在一个无头浏览器中尝试过这个方法,并且它对我有效。但是,如果您在像Chrome这样的浏览器中运行mocha测试,那么这种方法将不起作用。 - Danny Andrews

14

桩对象只能替换函数,而不能替换属性。

抛出的错误提示进一步强化了这一点:

TypeError: Custom stub should be a function or a property descriptor

根据文档:

何时使用桩对象?

使用桩对象当你想要:

  1. 从测试中控制方法的行为,以强制代码按特定路径执行。例如,强制一个方法抛出错误以测试错误处理。

  2. 防止直接调用特定方法(可能会触发不希望出现的行为,如 XMLHttpRequest 或类似情况)。

http://sinonjs.org/releases/v2.0.0/stubs/


可能的解决方案

虽然许多内置对象都可以被替换(进行测试),但有些不能。对于那些属性,您可以创建外观对象,然后在代码中使用它们并能够在测试中替换它们。

例如:

var loc = {

    setLocationHref: function(newHref) {
        window.location.href = newHref;
    },

    getLocationHref: function() {
        return window.location.href;
    }

};

使用方法:

loc.setLocationHref('http://acme.com');
你可以在测试中编写。
var stub = sinon.stub(loc, 'setLocationHref').returns('http://www.foo.com');

注意到代码中连续的returns()调用。你的代码还有另外一个错误:第三个参数必须是一个函数,而不是另一种类型的值。这是一个回调函数,而不是属性应该返回的内容。

查看stub()的源代码


1
谢谢 @try-catch-finally。 您的回答确实是正确的,我已经将其标记为正确。但不幸的是,我需要测试一些代码,该代码的行为直接依赖于 window.location.href 值。 由于我正在使用 Mocha html 运行程序在浏览器中进行测试,是否有任何方法可以以某种方式创建 window.location.href 的值,以便测试的代码可以访问该值,而无需让浏览器跟随 URL? - Francesco Pezzella
很遗憾,没有。但我想到另一个方法:配置底层Web服务器以在任何路径返回测试套件运行器,并使测试框架不断将结果保存到本地存储并在页面加载(重定向)时重新加载它。当然,您不应该重定向到“foo.com”,而是“/foo/dir”。请在Stackoverflow上搜索此主题的类似问题,如果找不到答案,请提出另一个问题(与此问题一样清晰明了 :))。 - try-catch-finally
1
我们在 Sinon 问题跟踪器上经常给人们的另一种选择是,在客户端代码中使用 wrapple 等全局对象的现成门面层,然后桩替换其方法。这样也可以达到同样的效果。 - oligofren

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接