promise-test-helper

azu/promise-test-helper という名前そのままですが、
Mocha等でPromiseのテストを書くときに見落としを減らすための補助ライブラリを書きました。

MochaのPromiseテストというのは、下記のようにpromiseオブジェクトを返すとそれをPromiseのテストと認識してやってくれる機構の事を言っています。

it("should support by mocha", function () {
    return getSuccessPromise().then(function (value) {
        assert(value);
    });
});

詳しくは下記を見て下さい。

Promiseのテストの難しさ

Promiseのテストについてはazu/promises-bookChapter.3 – Promisesのテストで詳しく書いているので、こちらを読んで欲しいのですが簡単にハマりどころを紹介します。
(間違いがあったりもっといい方法があったらIssues · azu/promises-bookにissue下さい)

必ず通ってしまうテスト

例えば、次のようなpromiseオブジェクトがRejectedされた時の結果をテストしたい場合、
必要最小限だと以下のように書いてしまうと思います。

function mayBeRejected(){
    return Promise.resolve();
}
it("is bad pattern", function () {
    return mayBeRejected().catch(function (error) {
        assert(error instanceof Error);
    });
});

しかし、このテストはmayBeRejected()がFulfilledされてしまうと、そもそもcatchが呼ばれなくなるので必ずテストがパスしてしまいます。

これを防止するためには、promiseオブジェクトがfulfilledされた時は、
テストが失敗するということを明示する必要があります。

function failTest() { 
    throw new Error("Expected promise to be rejected but it was fulfilled");
}
function mayBeRejected(){
    return Promise.resolve();
}
it("thenで落ちる条件を明示する", function () {
    return mayBeRejected().then(failTest, function (error) {
        assert(error instanceof Error);
    });
});

こうすることでfulfilledされた時はfailTestが呼ばれるのでテストが意図したように落ちます。
(3.2. MochaのPromisesサポートで詳しく解説してます)

promise-test-helperで書く

promise-test-helperではpromiseオブジェクトがどちらの状態になるのかを明示するためのヘルパー関数を提供しています。

var shouldFulfilled = require("promise-test-helper").shouldFulfilled;
var shouldRejected = require("promise-test-helper").shouldRejected;

という2つです。

これを使うと先ほどのテストは以下のように書けます。

var shouldRejected = require("promise-test-helper").shouldRejected;
function mayBeRejected(){
    return Promise.resolve();
}
it("should be failed", function () {
    return shouldRejected(mayBeRejected()).catch(function (error) {
        assert(error instanceof Error);
    });
});

この場合は、fulfilledされた時点で自動でテストが落ちるようになるので、自動てテストが通ってしまうミスが減ると思います。
(一番の罠はreturn忘れですが…)

他のやり方

3.3. 意図したテストを書くにはのまとめでも簡単に触れていますが、
PromiseのテストでもMochaのdoneを使った方法も使えるので、そちらでもPromiseのテストが行えます。

Promise(又は非同期)のテストの難しさには3つのものがあると思います。

  • 意図してない状態(onFulfilledかonRejected)になった時にどうするかを明示する必要性
    • 意図してない状態になった時にテストが自動で通ってしまう
  • MochaのPromisesサポートを利用した際にreturnをし忘れる
  • doneを使ったテストをするときに、仮引数にdoneを書き忘れる

というのが主なものだと思います。

この辺はPromiseの仕様書く人たちでもうっかりすることがあるので、
Promise(又は非同期)のテストでは、”最初に落ちるテストを書く”というのがより重要になるなーと思いました。

他の解決方法として、QUnitexpect()のようにassertが呼ばれる個数を明示することで、
ミスして自動でパスしてしまうテストを減らす事ができます。
(逆にテストをリファクタリングするときに面倒になる可能性も持っていますが)


今回紹介したpromise-test-helperazu/promises-bookで使ってるので、サンプル的なものはリポジトリにあるテストを参照するといいかもしれません。