Promises/A+ logo

用于 Promise 的 Chai 断言

Chai as Promised 扩展了 Chai,提供了一种流畅的语言,用于断言关于 promises 的事实。

您无需手动将期望值连接到 promise 的 fulfilled 和 rejected 处理程序

doSomethingAsync().then(
    function (result) {
        result.should.equal("foo");
        done();
    },
    function (err) {
       done(err);
    }
);

您可以编写表达您真正意图的代码

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

或者,如果您遇到 return 不合适(例如,样式考虑因素)或不可行(例如,测试框架不允许返回 promise 来指示异步测试完成)的情况,则可以使用以下解决方法(其中 done() 由测试框架提供)

doSomethingAsync().should.eventually.equal("foo").notify(done);

注意:promise 断言必须使用 returnnotify(done)。这可能与项目或团队使用的现有断言格式略有不同。这些其他断言可能是同步的,因此不需要特殊处理。

如何使用

should/expect 接口

Chai as Promised 提供的最强大的扩展是 eventually 属性。使用它,您可以将任何现有的 Chai 断言转换为对 promise 进行操作的断言

(2 + 2).should.equal(4);

// becomes
return Promise.resolve(2 + 2).should.eventually.equal(4);


expect({ foo: "bar" }).to.have.property("foo");

// becomes
return expect(Promise.resolve({ foo: "bar" })).to.eventually.have.property("foo");

还有一些针对 promise 的扩展(也提供了通常的 expect 等效项)

return promise.should.be.fulfilled;
return promise.should.eventually.deep.equal("foo");
return promise.should.become("foo"); // same as `.eventually.deep.equal`
return promise.should.be.rejected;
return promise.should.be.rejectedWith(Error); // other variants of Chai's `throw` assertion work too.

assert 接口

should/expect 接口一样,Chai as Promised 为 chai.assert 提供了一个 eventually 扩展器,允许对 promise 使用任何现有的 Chai 断言

assert.equal(2 + 2, 4, "This had better be true");

// becomes
return assert.eventually.equal(Promise.resolve(2 + 2), 4, "This had better be true, eventually");

当然,也有一些针对 promise 的扩展

return assert.isFulfilled(promise, "optional message");

return assert.becomes(promise, "foo", "optional message");
return assert.doesNotBecome(promise, "foo", "optional message");

return assert.isRejected(promise, Error, "optional message");
return assert.isRejected(promise, /error message regex matcher/, "optional message");
return assert.isRejected(promise, "substring to search error message for", "optional message");

进度回调函数

Chai as Promised 本身不支持测试 promise 进度回调函数。您可能想要测试的属性可能更适合使用像 Sinon.JS 这样的库,也许可以结合使用 Sinon–Chai

var progressSpy = sinon.spy();

return promise.then(null, null, progressSpy).then(function () {
    progressSpy.should.have.been.calledWith("33%");
    progressSpy.should.have.been.calledWith("67%");
    progressSpy.should.have.been.calledThrice;
});

自定义输出 Promise

默认情况下,Chai as Promised 的断言返回的 promise 是普通的 Chai 断言对象,扩展了从输入 promise 继承的单个 then 方法。要更改此行为,例如要输出具有更多实用糖方法的 promise(例如,大多数 promise 库中发现的),您可以覆盖 chaiAsPromised.transferPromiseness。以下示例演示了 Q 的 finallydone 方法的传输

import {setTransferPromiseness} from 'chai-as-promised';

setTransferPromiseness(function (assertion, promise) {
    assertion.then = promise.then.bind(promise); // this is all you get by default
    assertion.finally = promise.finally.bind(promise);
    assertion.done = promise.done.bind(promise);
});

将参数转换为断言器

Chai as Promised 允许的另一个高级自定义挂钩是,如果您希望转换断言器的参数,可能是异步的。这是一个玩具示例

import {transformAsserterArgs} from 'chai-as-promised';

setTransformAsserterArgs(function (args) {
    return args.map(function (x) { return x + 1; });
});

Promise.resolve(2).should.eventually.equal(2); // will now fail!
Promise.resolve(3).should.eventually.equal(2); // will now pass!

转换甚至可以是异步的,返回一个数组的 promise 而不是直接返回数组。这方面的一个例子可能是使用 Promise.all,以便 promise 数组变为数组的 promise。如果您这样做,则可以使用断言器将 promise 与其他 promise 进行比较

// This will normally fail, since within() only works on numbers.
Promise.resolve(2).should.eventually.be.within(Promise.resolve(1), Promise.resolve(6));

setTransformAsserterArgs(function (args) {
    return Promise.all(args);
});

// But now it will pass, since we transformed the array of promises for numbers into
// (a promise for) an array of numbers
Promise.resolve(2).should.eventually.be.within(Promise.resolve(1), Promise.resolve(6));

兼容性

Chai as Promised 兼容所有遵循 Promises/A+ 规范 的 promise。

值得注意的是,jQuery 的 promise 在 jQuery 3.0 之前没有达到规范,Chai as Promised 无法与它们一起使用。特别是,Chai as Promised 广泛使用了 then 的标准 转换行为,而 jQuery<3.0 不支持它。

Angular promise 具有用于其处理的特殊消化周期,并且 需要额外的设置代码才能与 Chai as Promised 一起使用

使用不友好的 Promise 测试运行器

一些测试运行器(例如 Jasmine、QUnit 或 tap/tape)无法使用返回的 promise 来指示异步测试完成。如果可能,我建议切换到支持此功能的运行器,例如 MochaBusterblue-tape。但如果这不是一种选择,Chai as Promised 仍然可以提供帮助。只要您的测试框架采用回调函数来指示异步测试何时结束,Chai as Promised 就可以使用其 notify 方法适应这种情况,如下所示

it("should be fulfilled", function (done) {
    promise.should.be.fulfilled.and.notify(done);
});

it("should be rejected", function (done) {
    otherPromise.should.be.rejected.and.notify(done);
});

在这些示例中,如果条件不满足,测试运行器将收到以下形式的错误: "expected promise to be fulfilled but it was rejected with [Error: error message]""expected promise to be rejected but it was fulfilled."

还有另一种形式的 notify,它在某些情况下非常有用,例如在 promise 完成后进行断言。例如

it("should change the state", function (done) {
    otherState.should.equal("before");
    promise.should.be.fulfilled.then(function () {
        otherState.should.equal("after");
    }).should.notify(done);
});

注意 .notify(done) 如何直接挂在 .should 上,而不是出现在 promise 断言之后。这指示 Chai as Promised 将 fulfilled 或 rejection 直接传递给测试框架。因此,如果 promise 被拒绝,上面的代码将使用 Chai as Promised 错误 ("expected promise to be fulfilled…") 失败,但如果 otherState 未发生更改,则将使用简单的 Chai 错误 (expected "before" to equal "after") 失败。

使用 async/await 和友好的 Promise 测试运行器

由于任何必须等待 promise 的断言本身都会返回一个 promise,因此,如果您能够使用 async/await 并且您的测试运行器支持从测试方法返回 promise,则可以在测试中等待断言。在许多情况下,您可以通过在 await 之后执行同步断言来完全避免使用 Chai as Promised,但等待 rejectedWith 通常比在没有 Chai as Promised 的情况下使用 try/catch 块更方便

it('should work well with async/await', async () => {
  (await Promise.resolve(42)).should.equal(42)
  await Promise.reject(new Error()).should.be.rejectedWith(Error);
});

多个 Promise 断言

要对多个 promise 执行断言,请使用 Promise.all 来组合多个 Chai as Promised 断言

it("should all be well", function () {
    return Promise.all([
        promiseA.should.become("happy"),
        promiseB.should.eventually.have.property("fun times"),
        promiseC.should.be.rejectedWith(TypeError, "only joyful types are allowed")
    ]);
});

这会将单个 promise 断言的任何失败传递给测试框架,而不是像您执行 return Promise.all([]).should.be.fulfilled 时那样将其包装在 "expected promise to be fulfilled…" 消息中。如果您无法使用 return,则使用 .should.notify(done),类似于前面的示例。

安装和设置

节点

执行 npm install chai-as-promised 开始使用。然后

import * as chai from 'chai';
import chaiAsPromised from 'chai-as-promised';

chai.use(chaiAsPromised);

// Then either:
const expect = chai.expect;
// or:
const assert = chai.assert;
// or:
chai.should();
// according to your preference of assertion style

您当然可以在公共测试夹具文件中放置此代码;有关使用 Mocha 的示例,请参阅 Chai as Promised 测试本身

使用其他 Chai 插件时的注意事项:Chai as Promised 会在安装时查找所有当前注册的断言器并对其进行 promisify。因此,如果您希望对断言器进行 promisify,则应在任何其他 Chai 插件之后安装 Chai as Promised。

Karma

如果您使用的是 Karma,请查看随附的 karma-chai-as-promised 插件。

浏览器/节点兼容性

Chai as Promised 需要支持 ES 模块和现代 JavaScript 语法。如果您的浏览器不支持此功能,则需要使用像 Babel 这样的工具将其转译。