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 アサーションでは、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.asserteventually エクステンダを提供し、既存の Chai アサーションを Promise で使用できるようにします。

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 は、入力 Promise から派生した単一の then メソッドで拡張された通常の Chai アサーションオブジェクトです。この動作を変更するには、たとえば、ほとんどの 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 が許可するもう1つの高度なカスタマイズフックは、アサータへの引数を非同期で変換したい場合です。おもちゃの例を以下に示します。

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 3.0 より前の jQuery の Promise は仕様に準拠しておらず、Chai as Promised はそれらと動作しません。特に、Chai as Promised は、then の標準的な 変換動作 を広く使用しており、jQuery<3.0 はこれをサポートしていません。

Angular の Promise は、処理に特別なダイジェストサイクルを持っており、Chai as Promised と連携するには追加のセットアップコードが必要です

Promise に対応していないテストランナーとの連携

一部のテストランナー(例:Jasmine、QUnit、または tap/tape)には、返された Promise を使用して非同期テストの完了を知らせる機能がありません。可能であれば、MochaBuster、または 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) が Promise アサーションの後ではなく、.should に直接ぶら下がっていることに注意してください。これは、Chai as Promised に、充足または拒否をテストフレームワークに直接渡すように指示します。したがって、上記のコードは、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 を使用できない場合は、前の例と同様に .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 は、インストール時に、現在登録されているすべてのアサータを検出して Promise 化します。したがって、アサータを Promise 化する必要がある場合は、他の Chai プラグインの後で、Chai as Promised を最後にインストールする必要があります。

Karma

Karma を使用している場合は、付属の karma-chai-as-promised プラグインを確認してください。

ブラウザ/Node 互換性

Chai as Promised は、ES モジュールと最新の JavaScript 構文のサポートが必要です。ブラウザがこれをサポートしていない場合は、Babel などのツールを使用してトランスパイルする必要があります。