【问题标题】:How can I stub/mock a function in the javascript global namespace如何在 javascript 全局命名空间中存根/模拟函数
【发布时间】:2010-12-20 01:51:07
【问题描述】:

我正在尝试在将日志写入数据库的测试期间存根/模拟/覆盖函数调用。

function logit(msg) {
  writeMessageToDb(msg);
}

function tryingToTestThisFunction(){
  var error = processSomething();
  if (error) {
    logit(error);
  }
}

我希望logit() 在测试期间简单地打印到控制台...并且在logit() 函数内执行“isTesting()”if/else 块不是一个选项。

这是否可能不包括一些额外的模拟框架。我目前正在使用JsTestDriver 进行单元测试,并且没有机会评估任何模拟框架。目前一个理想的解决方案是在没有其他框架的情况下处理这个问题。

【问题讨论】:

    标签: javascript testing mocking


    【解决方案1】:

    我使用 Jasmine 和 Sinon.js(使用 Coffeescript),下面是我如何将 confirm() 方法存根,例如,只返回 true。

    beforeEach ->
      @confirmStub = sinon.stub(window, 'confirm')
      @confirmStub.returns(true)
    
    afterEach ->
      @confirmStub.restore()
    

    【讨论】:

    • 这种情况不同,因为 Window.confirm() 不是全局函数。
    • 它本质上一个全局函数。在控制台中尝试confirm('test')window 本身就是全局对象(甚至公开了window.window——以及全局定义的用户函数)。在 Node 中,global 将是所需的目标(例如,存根 setTimeout 不会永远占用)。或者,如果在不需要 IE 支持或未更新浏览器版本的 Node 12+ 上,请使用 globalThis
    【解决方案2】:

    在 javascript 中,最新的定义是流行的。

    所以只需在第一次定义之后重新定义 logit 方法即可。

    function logit(msg) {
      console.log(msg);
    }
    

    示例:http://www.jsfiddle.net/gaby/UeeQZ/

    【讨论】:

      【解决方案3】:

      我一直在研究完全相同的问题。开发人员给了我一个 HTML5 应用程序来测试,所以我当然不能更改他们的测试代码。我决定使用qunitsinon,以及 sinon-qunit。

      对于像我这样的 JavaScript 单元测试新手,我对 sinon 文档和 Web 上的各种示例非常着迷,因为其中大部分似乎是针对未提及的隐含环境。下面的代码是一个完整的页面,所以我希望没有留下任何混淆。

      我必须调用的函数是caller(),而我对stubme() 无能为力,因为它在开发人员的代码中。但是,我可以在我的测试代码中添加sinonstub()。但是如何让它与 sinon 一起工作呢? sinon 文档确实让我困惑了一段时间,但下面是简单的解决方案。 stub4stubme 对象可用于控制存根操作,还可以获取有关存根调用所发生情况的信息。

      <!DOCTYPE html>
      
      <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
      <head>
          <meta charset="utf-8" />
          <title></title>
          <link rel="stylesheet" href="qunit-1.12.0.css" type="text/css" media="screen" />
      </head>
      <body>
          <div id="qunit"></div>
          <div id="qunit-fixture"></div>
          <script src="sinon-1.7.3.js"></script>
          <script src="qunit-1.12.0.js"></script>
          <script src="sinon-qunit-0.8.0.js"></script>
          <script>
      
              // Dev code in another file
              function stubme() {
                  return "stubme";
              }
      
              function caller() {
                  return "caller " + stubme();
              }
              // End of dev code
      
              var sinonstub = function () {
                  return "u haz bin stubbed";
              };
      
              test("Stubbing global environments", function () {
                  equal(caller(), "caller stubme");
      
                  var stub4stubme = this.stub(window, "stubme", sinonstub);
      
                  equal(caller(), "caller u haz bin stubbed");
                  ok(stubme.called);
              });
      
          </script>
      </body>
      </html>
      

      【讨论】:

      • 你几乎明白我的困惑了。 Sinon.stub(window, "stubme", sinonstub);为什么“窗口”是顶级对象名称?这里有一些关于全局变量环境的事情,在您的示例中称为窗口,因为它在窗口中运行。但是原始海报可能在 Node.js 中,或者,.... 那么它仍然是“窗口”吗?
      【解决方案4】:

      Javascript 不仅是 runtime 链接的,而且是 the last word wins 链接的。这意味着您可以在测试中使用您想要的行为重新声明该方法(因为您的测试有最后一个字):

      function yourTest(){
          oldImpl = logit;   // An even better approach is to do this in a setup.
          logit = function(msg){ Console.log.apply(console, s.call(arguments));};
          // do you assertions: assert.... yada.
      
          logit = oldImpl;  // Do this to keep your test isolated from the others you'll be executing in this test run. An even better approach is to do this in a teardown.
      }
      

      【讨论】:

      • 这是唯一正确的答案。当您想要对覆盖的函数进行单元测试时,上面的那些只会带来混乱,因为它没有原始功能。
      【解决方案5】:

      你能覆盖窗口对象的方法吗?在 Chrome 控制台中这是可行的

      function test() {console.log('test')};
      window.test();
      

      【讨论】:

        【解决方案6】:

        只需重写 logit 函数,它可以在 logit 定义之后的任何时间调用。

        (function(){
          //keep handle to original logit method.
          var ol = logit;
        
          //shorter lookup path for slice
          var s = Array.prototype.slice;
        
          //logit override
          logit = function() {
            //if in testing
            if (typeof IsTesting == "function" && !!IsTesting()) {
              //log the arguments
              console.log.apply(console, s.call(arguments));
            } else {
              //otherwise, call the original function.
              ol.apply(this, s.call(arguments))
            }
          }
        }());

        【讨论】:

        • 注意:您可以将“msg”指定为参数并直接传递,方法上带有 .apply 的 s.call(arguments) 将传递所有内容,这对于像这样的方法覆盖更有效.
        猜你喜欢
        • 2013-11-19
        • 1970-01-01
        • 2013-08-11
        • 1970-01-01
        • 2012-11-07
        • 2016-08-03
        • 1970-01-01
        • 2019-11-15
        • 1970-01-01
        相关资源
        最近更新 更多