yo_waka's blog

418 I'm a teapot

Node.jsアプリで結果が非同期になる箇所はコールバックを渡せるようにした方がいい

既存のNodeアプリのテストを書いていて思ったことをメモ。
書いてるうちに別にNode.jsに限った話じゃなくて、JavaScript全般に言える話じゃんと思ったけどまいっか。

Node.jsで書くアプリは中の処理で非同期API使われてると、テストを書くのがとても難しいというかめんどくさい。

例えばfsモジュールで非同期でファイル書いてる処理があって、もし書き込みに失敗したらエラーイベントを投げて上のレイヤーで処理する、みたいなのがあるとする。

// HogeクラスはEventEmitterを継承している
Hoge.prototype.writeFile = function(file, data) {
  var self = this;
  fs.writeFile(file, data, function(err) {
    if (err) {
      self.emit('error.file', err.message);
    }
  });
};

アプリ側では"error.file"イベントを捕まえてよしなにやるからいいんだけど、テストを書こうとするととてもめんどくさい。
ファイルに書き込んだら適当な時間待ちつつ、エラーイベントが捕まったら失敗にする。

// mochaを使った例
it('ファイル書けるか', function(done) {
  var timer;
  var hoge = new Hoge();
  hoge.on('error.file', function(errMessage) {
    clearTimeout(timer);
    assert.ok(false, errMessage);
  });
  hoge.writeFile('./test.txt', 'test');
  timer = setTimeout(function() {
    done();
  }, 1000);
});

適当な時間待つのがとてもスマートじゃない感じ。。。
これだけだったらいいけど、他にもいろんなところで非同期APIを中で使ってるとテストコードがsetTimeoutの嵐に><

この問題は、アプリ側の方でもコールバックを取れるようにしてあるといい感じにできる。

Hoge.prototype.writeFile = function(file, data, opt_callback) {
  var self = this;
  fs.writeFile(file, data, function(err) {
    if (err) {
      self.emit('error.file', err.message);
    }
    opt_callback && opt_callback(err || null);
  });
};

// テスト
it('ファイル書けるか', function(done) {
  var hoge = new Hoge();
  hoge.writeFile('./test.txt', 'test', function(err) {
    if (err) {
      assert.ok(false, err.message);
    }
    done();
  });
});

タイマー処理の必要がなくなり、とてもスッキリと書けるようになりました。
普段からNodeアプリを作ってる人には当たり前の話かもしれませんが、要は中で非同期API使ってるメソッドは外側からコールバックを渡せるようにしておくとテスト書きやすいですという話。
アプリで使ってなくても、オプション引数としてJSDocに書いておけばよいですし。
新しくNode.jsアプリを作るときはコーディング規約にしたいなー

既存のアプリですべての該当箇所を変えるのはそれはそれでいろいろ大変ですが。。