用于 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 断言必须使用 return
或 notify(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 的 finally
和 done
方法的传输
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 来指示异步测试完成。如果可能,我建议切换到支持此功能的运行器,例如 Mocha、Buster 或 blue-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 这样的工具将其转译。