【问题标题】:Configuring javascript unit testing and promises assertion配置 javascript 单元测试和 promise 断言
【发布时间】:2016-11-17 12:59:24
【问题描述】:

我是 javascript 单元测试的新手,但我相信至少对一般的单元测试有一点了解。

我正在尝试学习“javascript 方式”并开始为我的客户端代码编写单元测试。

为此,我安装了以下库: mocha.js 和 chai.js

在一些令人惊讶的投票中,一切进展顺利。 我设法轻松地包含了这些内容并开始编写我的单元测试。

顺便说一句,我在 ASP Core 工作。虽然我发现 VS2016 是我用过的这个 IDE 中最有问题的版本(但我已经成为一名专业人士只有 5 年,所以我可能错了)新的 MVC 6 更改它提供了一种简单的方法来管理你的静态资源(或客户端依赖项),让您使用 bower 包管理器。

所以,回到测试。我从一个小列表开始,列出了一些我已经放置的通用和简单的 javascript 函数。虽然写得有点糟糕,但他们提供了一个很好的练习。

但是,过了一会儿,我需要开始模拟了。通过用 C# 编写单元测试,我开始欣赏这种类型的库的有用性。 由于这些概念并非特定于 C#,而是通常用于单元测试,因此我希望找到一个能够提供这种功能的库 - sinon.js。

再说一遍,一切都很顺利。

直到,我不得不嘲笑一个承诺。

再次证明,有一个库可以帮助我解决这个问题 - sinon-as-promised.js

在通过 npm 安装它、使用 browserify 等工具将其解析为单个脚本以在浏览器中使用等相当多的努力之后,我相信我设法让它运行起来。

此时我可以执行以下操作:

let returnsPromise = sinon.stub($, "ajax").resolves('success');

这将使我成为一个“then-able”对象(具有 then() 方法并且应该表现为承诺的东西)。 这里有一个小问题,它没有 .done() 和 .fail() 方法,众所周知,我在编码中更喜欢这些方法,但我猜有一种简单的方法可以将它们扩展到对象中。

在那之后,当我读到 chai.js 提供了一种管理 Promise expactations 的有效方法时,我什至开始印象深刻。

我们来了,我的问题。

我所说的关于 chai 的事情是“最终”的支持。一个让我变魔术并防止在 then() 的成功和错误回调中使用 .done() 的词,只是为了确保我们不会在承诺中得到误报。

所以,它应该是这样使用的:

it('testing eventually', function () {
    let returnsPromise = sinon.stub($, "ajax").resolves('success');
    return expect(returnsPromise()).should.eventually.equal('success');        
});

如果发生任何与想要的结果不同的事情,这应该会失败。

现在,您可以想象当我无法按承诺运行 chai 时我的失望。

我找不到这个库的“浏览器友好”版本,所以在使用 npm 安装它之后,再次将它浏览器化为“应该在浏览器脚本中工作”我把它加入了聚会。

但是,无论我做什么,在上面的示例中,我都会收到以下错误:

TypeError: Cannot read property 'equal' of undefined
at Context.<anonymous> (js/miscellaneous-tests.js:94:58)

我将其解释为 - 我们不知道您所说的“最终”是什么意思。这让我相信我添加 chai-as-promised.js 的方式不起作用。

在这之后,我尝试了很多东西。有些愚蠢,有些甚至更愚蠢,但是无论我尝试了什么,它都无法让代码“最终”理解

这些内容包括:

  • 读取脚本并获取 chai-as-promised 的依赖项并将其添加到解决方案中(作为 browserify-ed 和它们来自 npm 的形式),在第二种情况下,再次对其依赖项执行相同操作 - 直到我相信没有丢失任何包裹。
  • 我尝试以多种不同的方式添加、删除文件,实际上我在写这篇文章时是在画一个空白 - 但我能想到的所有可能的组合 - 我都试过了。
  • 我在 Google 上搜索了很多次“最终未定义”之类的东西,我觉得 Google 有点生我的气,但我找不到我要找的东西
  • 在某个时候,我开始在地板上画一个带有五角星的大圆圈,但我花了一段时间(像处女的血和蜡烛这样的东西现在在办公室里不太容易找到)和第二天我回到办公室时,清洁工已经把它擦掉了。

由于我没有想法,我希望你们中的一些人尝试在浏览器中进行一些 javascript 单元测试。

因为它涉及到相当多的资源、解释和东西,而且这篇文章已经比它应该的要长,所以我创建了一个 github 存储库,我已经上传了我迄今为止所做的一切。

这里有一个链接: js-unit-test-mocking

顺便说一句,如果您不熟悉 ASP 或任何与微软相关的东西,那么运行它所需的一切都应该在 wwwroot 中。

感谢您对我的包容。抱歉,帖子太长了。

【问题讨论】:

    标签: javascript unit-testing mocking sinon chai


    【解决方案1】:

    你的代码有两个问题:

    1. 您正在同时使用expectshould 接口(@98​​7654324@。您应该使用其中一个,但不能同时使用两者。

    2. 您忘记使用chai-as-promised 的导出值调用chai.use。我从以下事实推断出这一点:如果我故意省略它,那么我会得到与你得到的完全相同的错误,而且我不知道有任何其他方法可以得到该错误。

    无论您想做什么,您必须chai-as-promised 转换为在浏览器中工作。既然您提到使用过browserify,我将在答案中使用它。您可以使用webpack 甚至编写自己的脚本来进行转换。

    接下来有两种通用策略。

    仅转换chai-as-promised

    这里的策略是将所有内容分开,仅将chai-as-promised 转换为在浏览器中工作。这是一个说明性的测试文件:

    const expect = chai.expect;
    mocha.setup("bdd");
    
    chai.should();
    
    // Commenting out this line reproduces the exact error message
    // reported in the question.
    chai.use(chaiAsPromised);
    
    // Incorrect use of should with expect.
    it('testing eventually incorrectly', function () {
        return expect(Promise.resolve("success")).should.eventually.equal('success');
    });
    
    // Correct use of expect alone.
    it('testing eventually with expect', function () {
        return expect(Promise.resolve("success")).eventually.equal('success');
    });
    
    // Correct use of should alone.
    it('testing eventually with should', function () {
        return Promise.resolve("success").should.eventually.equal('success');
    });
    

    chai-as-promised 使用此命令进行转换:

    browserify -o chai-as-promised-built.js -s chaiAsPromised node_modules/chai-as-promised/lib/chai-as-promised.js
    

    这实质上采用chai-as-promised.js 并将其包装到代码中,以便它在CommonJS 方言中正常导出的内容成为浏览器中的全局导出,并且该全局导出被命名为chaiAsPromised(这就是-s 选项的作用)。输出在chai-as-promise-built.js 中,这是您需要在浏览器中加载的内容。

    那么你就可以使用这个index.html文件了:

    <!DOCTYPE html>
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/xhtml; charset=utf-8"/>
        <link href="./node_modules/mocha/mocha.css" type="text/css" media="screen" rel="stylesheet" />
        <script type="text/javascript" src="./node_modules/mocha/mocha.js"></script>
        <script type="text/javascript" src="./node_modules/chai/chai.js"></script>
        <script type="text/javascript" src="./chai-as-promised-built.js"></script>
        <script type="text/javascript" src="./test.js"></script>
      </head>
      <body>
        <div id="mocha"></div>
        <script>
          mocha.run();
        </script>
      </body>
    </html>
    

    将所有内容打包到一个文件中

    这里的策略是使用 browserify 将所有内容编译成一个文件。对于某些项目来说,这是一个可行的选择。对于希望使用require 而不是依赖全局变量编写代码的项目,它也可能更可取。这是一个说明性的测试文件:

    const chai = require("chai");
    const chaiAsPromised = require("chai-as-promised");
    const expect = chai.expect;
    const mocha = require("mocha");
    mocha.mocha.setup("bdd");
    
    chai.should();
    
    // Commenting out this line reproduces the exact error message
    // reported in the question.
    chai.use(chaiAsPromised);
    
    // Incorrect use of should with expect.
    it('testing eventually incorrectly', function () {
        return expect(Promise.resolve("success")).should.eventually.equal('success');
    });
    
    // Correct use of expect alone.
    it('testing eventually with expect', function () {
        return expect(Promise.resolve("success")).eventually.equal('success');
    });
    
    // Correct use of should alone.
    it('testing eventually with should', function () {
        return Promise.resolve("success").should.eventually.equal('success');
    });
    

    安装所需模块后,使用以下命令构建它:

    browserify -o built.js test.js
    

    然后你可以用这个index.html文件加载它:

    <!DOCTYPE html>
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/xhtml; charset=utf-8"/>
        <link href="./node_modules/mocha/mocha.css" type="text/css" media="screen" rel="stylesheet" />
        <script type="text/javascript" src="./built.js"></script>
      </head>
      <body>
        <div id="mocha"></div>
        <script>
          mocha.run();
        </script>
      </body>
    

    您将看到第一个测试将失败,因为它尝试同时使用 expectshould。随后的两个测试将通过。

    如果您注释掉chai.use(chaiAsPromised); 的行,您会收到与您在问题中报告的完全相同的错误消息。如果您确实更改了代码,请记住使用 browserify 重建。

    【讨论】:

    • 感谢您的努力。首先,感谢您指出我没有正确使用应该/期望。我一定错过了你在函数本身而不是期望上使用 should 的事实。但是,从我最终看到的文档中并期望被用作:expect(promise).to.eventually ... - 使用“to”一词。我尝试了使用“to”和没有“to”,但仍然导致相同的错误。至于使用'chai.use(chaiAsPromised)' - 我不能这样做,因为我在浏览器中运行它。
    • 你的另一个想法是浏览我的测试脚本而不是用于测试的库。这可能确实有效,但我不希望实现它,因为我必须经常这样做而且它变得太很多开销。我不想写一些测试,浏览器化,运行,再次更改......等等。它有点太多了。我只想使用浏览器,没有nodejs。相反,因为我不能只“最终”工作,所以我决定使用 mocha 支持 promise 并返回 promise 对象,该对象考虑回调中的期望表达式,如果 promise 被拒绝则失败。
    • 你说:“我不能这样做,因为我在浏览器中运行它。”这是一个令人费解的回复,因为我的回答显然在浏览器中运行并调用chai.use(chaiAsPromised)
    • 浏览器什么时候支持require?你用的是什么浏览器?
    • 我用的是Chrome但是不支持require,没关系。关键是使用browserify 产生built.js。这就是浏览器中加载的内容。这样做不是可选的。 (在自述文件here 中提到。)哦,可以以不同方式 完成,但您不能跳过browserify 步骤。 (您可以使用功能等效的东西,例如webpack。我使用了browserify,因为您在问题中提到了它。)说您不能在浏览器中执行chai.use(chaiAsPromised) 是不正确的,因为我已经证明您可以.
    【解决方案2】:

    我的意思是,您可以通过使用返回延迟 jquery 的函数来存根 ajax 来使这种方式更简单。像下面这样的东西可以工作:

    var dfd = $.Deferred();
    var callback = sinon.spy();
    sinon.stub($, "ajax", function() {
       return dfd.promise();
    })
    $.ajax('...').done(callback);
    dfd.resolve();
    expect(callback).should.have.been.called();
    

    您还可以创建一个帮助器来为您执行此操作,以及一个始终将 ajax 替换为原始函数的“afterEach”。

    以上代码依赖于https://github.com/domenic/sinon-chai。如果您不想依赖 sinon-chai,那么您只需查看间谍的“callCount”即可。像这样:

    expect(callback.callCount > 0).to.be.ok;
    

    【讨论】:

      【解决方案3】:

      这是一个 chaiAsPromisedexpect 测试模式:

      var chai = require("chai");
      var expect = chai.expect;
      var chaiAsPromised = require("chai-as-promised");
      chai.use(chaiAsPromised);
      
      it("promise should return blah", function() {
        var blah = "blah";
      
        var result = somethingToTest();
      
        return expect(result).to.eventually.equal(blah);
      });
      

      在您的测试代码中,您混合了expectshould

      return expect(returnsPromise()).should.eventually.equal('success');
      

      编辑:来自 Chai API 文档:Chai Assertions for Promises

      return doSomethingAsync().should.eventually.equal("foo");
      

      遵循相同的模式,您的代码应该是:

      return returnsPromise().should.eventually.equal('success');
      

      【讨论】:

      • 浏览器不支持require,这就是我必须使用browserify工具的原因。就 ...to... 运算符而言,我也使用它,但仍然是同样的错误
      • 我已编辑我的答案以包含正确的测试模式。
      猜你喜欢
      • 1970-01-01
      • 2018-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-30
      • 1970-01-01
      • 2019-04-26
      相关资源
      最近更新 更多