配置 javascript 单元测试和承诺断言

Configuring javascript unit testing and promises assertion

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

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

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

在一些令人惊讶的投票率中,一切都进行得很顺利。 我设法轻松地包含了这些东西并开始编写我的单元测试。

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

所以,回到测试。我从一小部分通用和简单的 javascript 函数开始。虽然写得有点差,但它们提供了很好的练习。

然而,过了一会儿我需要开始嘲笑。通过使用 C# 编写单元测试,我开始意识到这种类型的库的实用性。 由于这些概念绝不特定于 C#,而是一般的单元测试,我希望找到一个库来提供这种功能 - sinon.js。

再一次,一切都很顺利。

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

同样,事实证明有一个库可以帮助我解决这个问题 - sinon-as-promised.js

经过相当多的努力,包括通过 npm 安装它,使用像 browserify 这样的工具将它解析成一个脚本以在浏览器中使用等等,我相信我设法得到它 运行ning。

此时我可以做类似的事情:

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

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

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

现在,我的问题来了。

我说的关于 chai 的是 "eventually" 支持。一个让我变魔术并避免在 then() 的成功和错误回调中使用 .done() 的词,只是为了确保我们不会在承诺中得到误报。

所以,应该这样使用:

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

如果没有成功获得想要的结果,这应该会失败。

现在,你可以想象当我无法像承诺的那样得到 chai 时我的失望 运行ning。

我找不到这个库的 'browser-friendly' 版本,所以,在使用 npm 安装它之后,再次将它浏览器化为 'supposed-to-be-working-in-the-browser-script' 我将它添加到聚会中。

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

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

我将其解释为 - 我们不知道您所说的 "eventually" 是什么意思。这让我相信我添加 chai-as-promised.js 的方式无效。

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

这些东西包括:

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

因为它涉及相当多的资源、解释和东西,而且这个 post 已经比它应该的长了很多,我创建了一个 github 存储库,我在其中上传了我所有的东西到此为止。

这是一个 link: js-unit-test-mocking

顺便说一句,如果您不喜欢 ASP 或任何与 Microsoft 相关的东西,运行 所需的一切都应该在 wwwroot 中。

谢谢你的包容。很抱歉 post.

这是一个 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');

您的代码有两个问题:

  1. 您正在同时使用 expectshould 界面 (expect(...).should.eventually.equal...)。您应该使用其中之一,但不能同时使用两者。

  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 进行重建。

我的意思是,您可以通过将 ajax 与 returns 和 jquery 延迟的函数打桩来使这种方式更简单。像下面这样的东西可以工作:

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;