Promiseで逐次実行する時のテスト

var Promise = require('es6-promise').Promise;

var tasks = {
    first: function(value) {
        return new Promise(function(resolve, reject) {
            if (value < 2) {
                resolve(value + 1);
            } else {
                reject(new Error('failed at first'));
            }
        });
    },
    second: function(value) {
        return new Promise(function(resolve, reject) {
            if (value % 2) {
                resolve(value + 1);
            } else {
                reject(new Error('failed at second'));
            }
        });
    },
    third: function(value) {
        return new Promise(function(resolve, reject) {
            resolve(value + 1);
        });
    }
};

var waterfall = function(value) {
    return Promise.resolve(value)
        .then(tasks.first)
        .then(tasks.second)
        .then(tasks.third);
};

例えばこの関数waterfallが

  • 最後まで実行される
  • firstでrejectされる
  • secondでrejectされる

場合のテストケースをそれぞれ用意したい。
いろいろ試した結果、こんな感じになった。

var assert = require("power-assert");
var sinon = require('sinon');

describe('waterfall', function() {
    beforeEach(function() {
        sinon.spy(tasks, 'second');
        sinon.spy(tasks, 'third');
    });

    afterEach(function() {
        tasks.second.restore();
        tasks.third.restore();
    });

    it('passed', function(done) {
        return waterfall(0).then(function(value) {
            assert(value === 3);
        }).then(done, done);
    });

    it('failed at first', function(done) {
        return waterfall(2).then(function(value) {
            throw new Error(value);
        }, function(error) {
            assert(error.message === 'failed at first');
            assert(tasks.second.notCalled);
        }).then(done, done);
    });

    it('failed at second', function(done) {
        return waterfall(1).then(function(value) {
            throw new Error(value);
        }, function(error) {
            assert(error.message === 'failed at second');
            assert(tasks.third.notCalled);
        }).then(done, done);
    });
});

最初はspy.calledBeforeとかで実行順序全部チェックしてたけど、それはPromise自体のテストになってしまうので

  • 適切なエラーが投げられてる
  • reject後の関数が呼ばれてない

ことがテストできてれば良いかなと。 前提条件として投げるエラーはそれぞれ個別のmessageじゃないとrejectされた箇所を探すのは難しそう。
chaiならhttps://github.com/domenic/chai-as-promisedとか使うとスマートに書けるかも。