Promises/A+ logo

針對 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 斷言中必須使用 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);
});

轉換 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 來表示非同步測試完成。如果可以,我建議切換到支援此功能的測試執行器,例如 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 指示,它應該將 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 這樣的工具將其轉換為舊版語法。