針對 Promise 的 Chai 斷言
Chai as Promised 使用流暢的語法擴展了 Chai,以便對 promise 進行事實斷言。
您不必手動將您的預期與 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);
});
轉換 Asserter 的引數
Chai as Promised 允許的另一個進階自訂掛鉤是,如果您想要轉換 asserter 的引數,可能會以非同步方式進行。這是一個玩具範例
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 來表示陣列。如果您這樣做,則可以使用 asserter 來比較 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 指示,它應該將 fulfillment 或 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 斷言的所有失敗傳遞給測試框架,而不是將它們包裝在 "expected promise to be fulfilled…"
訊息中,如果您執行 return Promise.all([…]).should.be.fulfilled
則會發生這種情況。如果您無法使用 return
,則請使用 .should.notify(done)
,與先前的範例類似。
安裝與設定
Node
執行 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 在安裝時會找到所有目前已註冊的 asserter 並將它們 promisify。因此,如果您希望它們的 asserter 被 promisify,您應該在安裝任何其他 Chai 外掛之後,最後安裝 Chai as Promised。
Karma
如果您使用 Karma,請查看隨附的 karma-chai-as-promised 外掛。
瀏覽器/Node 相容性
Chai as Promised 需要支援 ES 模組和現代 JavaScript 語法。如果您的瀏覽器不支援此語法,您需要使用像 Babel 這樣的工具將其轉換為舊版語法。