内部に隠蔽されたPromiseにいかにAssertionErrorを補足させないようにするか問題

More than 1 year has passed since last update.

勢い余っていろいろと書きましたが、読み返してみると意味がわからなかったので本当に書きたかったことだけにしました。

実装例のように内部にPromiseが隠蔽されてるけれどもコールバックの呼び出しはthen内で同期的に行われる的な関数のテストが辛いのでいい方法を探しています。

※ nodejsのバージョンはv4.3.2です

"use strict";

//実際はもっと奥深くにPromiseが隠蔽されている
function asyncCallback(callback) {
new Promise(function(resolve, reject){
setImmediate(function(){
resolve(1);
})
}).then(function(v){
callback(null, v);
}, function(err){
callback(err);
});
}

実際は各テストコードに上記の関数が定義されています。


テスト1(ダメなパターン)

このままだとコールバックが呼ばれる前に処理が終わってしまうので、AssertionErrorは発生していますがfailしません。

"use strict";

const assert = require('assert');

describe("promise callback", function(){
it("bad example", function(){
asyncCallback(function(err, v){
assert.equal(v, 2);
});
});
});

$ mocha test/test.js

promise callback
✓ bad example

1 passing (8ms)


テスト2(ダメなパターン)

doneを受け取れば問題ないかと思いましたが、AssertionErrorをPromiseが補足してしまうので、Mochaに届かないようです。

"use strict";

const assert = require('assert');

describe("promise callback", function(){
it("bad example", function(done){
asyncCallback(function(err, v){
assert.equal(v, 2);
done();
});
});
});

$ mocha test/test.js

promise callback
1) bad example

0 passing (2s)
1 failing

1) promise callback bad example:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.


テスト3 (動くパターン)

Promiseに渡る前に自らtry/catchしてしまえば動きます。

何度もtry/catchを書きたくないのであまりいい方法とは思えません。

"use strict";

const assert = require('assert');

describe("promise callback", function(){
it("good example", function(done){
asyncCallback(function(err, v){
try {
assert.equal(v, 2);
done();
} catch (e) {
done(e);
}
});
});
});

$ mocha test/test.js

promise callback
1) bad example

0 passing (10ms)
1 failing

1) promise callback bad example:

AssertionError: 1 == 2
+ expected - actual

-1
+2

at test/test.js:21:16
at test/test.js:11:5


テスト4(動くパターン)

assertの呼び出し部分を非同期にすることでPromiseに例外を補足されないようにしています。

こちらの方が実装3よりも個人的には好きです。

エラーが「Uncaught AssertionError」になってしまうので、実装3のエラー「AssertionError」と比べると実装3の方がいいのかもしれません。

"use strict";

const assert = require('assert');

describe("promise callback", function(){
it("good example", function(done){
asyncCallback(function(err, v){
setImmediate(function(){
assert.equal(v, 2);
done();
});
});
});
});

$ mocha test/test.js

promise callback
1) bad example

0 passing (9ms)
1 failing

1) promise callback bad example:

Uncaught AssertionError: 1 == 2
+ expected - actual

-1
+2

at Immediate._onImmediate (test/test.js:21:16)


テスト5(解決!) 2016/10/13追記

@gaogao_9さんにコメントいただいた内容を追加しています。

@gaogao_9さんありがとうございます。

Promiseが隠蔽されて戻らないならそのコールバックをPromiseで包んでreturnしてあげます。

テスト3/4で問題と思っていた部分のほとんどが解消しました。


  • テスト3のtry/catchを書かないといけない問題の解消

  • テスト4のAssertionErrorがUncaughtになってしまう問題の解消

  • テスト3/4のdoneが必須なところの解消

"use strict";

const assert = require('assert');

function asyncCallback(callback) {
new Promise(function(resolve, reject){
setImmediate(function(){
resolve(1);
})
}).then(function(v){
callback(null, v);
}, function(err){
callback(err);
});
}

function promisify(callback) {
return new Promise(function(resolve, reject){
callback(function(err, v) {
if (err) reject(err);
else resolve(v);
});
});
}

describe("promise callback", function(){
it("good example", function(){
return promisify(asyncCallback).then(function(v){
assert.equal(v, 2);
});
});
});

$ mocha test/test.js

promise callback
1) bad example

0 passing (10ms)
1 failing

1) promise callback bad example:

AssertionError: 1 == 2
+ expected - actual

-1
+2

at test/test.js:29:14

思いつきですが、itのコールバックの引数が、引数なし/引数がエラーオブジェクトならdoneの動作、引数が関数の場合にはpromisifyの動作とか動いてくれたら良さそうな気がしました。

こんなイメージです。

describe("promise callback", function(){

it("good example", function(promisify){
return promisify(asyncCallback).then(function(v){
assert.equal(v, 2);
});
});
});

まあ、こんなことしなくても教えていただいたawaitが使えるようになれば全部解決ですけど。:smile:


まとめ

以上、隠蔽されたPromiseにいかにAssertionErrorを補足させないようにするか問題でした。