Mochaを使ってBluebirdを利用したコードのテストを行おうとした際、正しくテスト結果が得られない場合があったので原因と対策をメモ。
まず、バグを含んだ関数を用意する。
promise-add.js
var Promise = require('bluebird')
module.exports = function promiseAdd(a, b) { // 2つの値を足し算する関数
return new Promise(function(resolve, reject) {
resolve(a + b + 1) // 正しい結果に+1されてしまうバグ!
})
}
次に、上記の関数のテストを用意する。
promise-add-test.js
var assert = require('assert')
var promiseAdd = require('./promise-add.js')
describe('promise-add', function() {
it('promiseAdd(1, 1) == 2', function(done) { // promiseAdd(1, 1)の結果が2であることをテストする
promiseAdd(1, 1).then(function(result) {
assert.equal(result, 2) // result == 3なので、テスト失敗!
done()
})
})
})
結果は、timeoutとなり期待していた結果(AssertionError)が得られない。
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
さらに、例外として「Umhandled rejection」が投げられてしまう。
Unhandled rejection AssertionError: 3 == 2
at promise-add-test.js:7:14
at tryCatcher (node_modules/bluebird/js/main/util.js:26:23)
at Promise._settlePromiseFromHandler (node_modules/bluebird/js/main/promise.js:503:31)
at Promise._settlePromiseAt (node_modules/bluebird/js/main/promise.js:577:18)
at Promise._settlePromiseAtPostResolution (node_modules/bluebird/js/main/promise.js:244:10)
at Async._drainQueue (node_modules/bluebird/js/main/async.js:128:12)
at Async._drainQueues (node_modules/bluebird/js/main/async.js:133:10)
at Immediate.Async.drainQueues [as _onImmediate] (node_modules/bluebird/js/main/async.js:15:14)
at processImmediate [as _immediateCallback] (timers.js:358:17)
1) 1 + 1 = 2
原因
bluebirdのPromiseは、例外が投げられたりreject関数が呼ばれたりするとcatch handlerに飛ぶ仕組みになっている。そのため、assertを呼び出しエラーが起きた時点でcatch handlerに飛ぼうとするため、後続のdoneが実行されずにtimeoutとなる。
さらに、catch handlerを記述していないため「Unhandled rejection」が表示されてしまう。
promise-add-test.js
var assert = require('assert')
var promiseAdd = require('./promise-add.js')
describe('promise-add', function() {
it('promiseAdd(1, 1) == 2', function(done) { // promiseAdd(1, 1)の結果が2であることをテストする
promiseAdd(1, 1).then(function(result) {
assert.equal(result, 2) // result == 3なので、テスト失敗! -> catch handlerへ飛ぶ
done() // !!!呼ばれない!!!
})
})
})
対策
assertによって投げられたエラーは後続のcatch handlerで受け取れるので、catch handlerの中でdoneを呼び出すように変更する。
promise-add-test.js
var assert = require('assert')
var promiseAdd = require('./promise-add.js')
describe('promise-add', function() {
it('promiseAdd(1, 1) == 2', function(done) { // promiseAdd(1, 1)の結果が2であることをテストする
promiseAdd(1, 1).then(function(result) {
assert.equal(result, 2) // result == 3なので、テスト失敗! -> catch handlerへ飛ぶ
done() // !!!呼ばれない!!!
}).catch(function (e) { // catch handlerを追加
done(e) // 呼ばれる
})
})
})
これを実行すると、timeoutにならずきちんとAssertionErrorが表示される。
AssertionError: 3 == 2
+ expected - actual
-3
+2