MatadorのControllerをMochaでテストする

ひょんなことからnode.jsで開発をすることになりました。仕事でじゃないですけど。

matadorっていうフレームワークとMochaっていうテストフレームワークで開発するんですが、とりあえずエンドツーエンドのテストを書きながら実装をしようとしたところ早速うまくいかず・・・。
node素人が何とかテストを動かすまでの備忘録です。


Matadorを動かすのは簡単でした。MatadorのGithubにある手順どおりやればすぐにWebアプリが動く。
使用したMatadorのバージョンは、1.1.22-beta。
Githubの手順どおりにやると作成されるHomeControllerに対するテストを書いてみるか。

テストはMochaってのを使う。
さらに、supertestっていうライブラリを使ってControllerのテストができるっぽいことが分かった。
使用したMochaのバージョンは1.8.1、supertestのバージョンは0.5.1。

プロジェクトルート/test/functionals/HomeControllerTest.js というファイルを作成して試す。

var app = require('matador').createApp(__dirname + '/../../')
   ,should = require('should')
   ,request = require('supertest');
describe('HomeControllerのテスト', function() {
  it('その1', function() {
    request(app)              
      .get('/')
      .expect(200)
      .end(function(err, res){
        if(err) throw err;
      });
  }
}


テスト実行。

$ mocha test/functionals/HomeControllerTest.js

  .

  1 test complete (27 ms)

おっ!うまくいった!

試しに失敗させてみよう。ステータスコードとして2000が返ってくることを検証するように書き換えてみる。

var app = require('matador').createApp(__dirname + '/../../')
   ,should = require('should')
   ,request = require('supertest');
describe('HomeControllerのテスト', function() {
  it('その1', function() {
    request(app)              
      .get('/')
      .expect(2000)
      .end(function(err, res){
        if(err) throw err;
      });
  }
}


テスト実行。

$ mocha test/functionals/HomeControllerTest.js

  .

  1 test complete (27 ms)

・・・。
テストが失敗しない・・・。

検証がちゃんと行われていないのか?もしかしてexceptがちゃんと呼ばれていないんじゃ。
supertestのソースにデバッグログでも埋め込んでみるか。
プロジェクトルート/node_modules/supertest/lib/test.js にexpectがあることを発見。
以下のように、console.logを埋め込んでみた。

Test.prototype.expect = function(a, b, c){
  console.log('debug');                   
  var self = this;
  
  ・・・
}


テスト実行。

$ mocha test/functionals/HomeControllerTest.js

  debug
  .

  1 test complete (22 ms)

おっ、ログが出た。ちゃんとexpectは呼ばれるみたい。
じゃあ何でテストが失敗しないの?と思いながらソース読んでみる。
expectの最初の方で、引数が数値1つの場合は、this._status = "引数の数値" というように代入だけして return this; してる。
expectでは設定だけして検証はしていないみたい。

そうすると、endを呼び出した時に検証するのか?
endのソースを追ってみる。
以下のような箇所を発見。self.assertの中で、expectで設定したステータスコードの検証を行なっているようだ。
またデバッグログを埋め込んでみる。

  end.call(this, function(res){
    console.log('debug');
    self.assert(res, fn);
  });
  return this;


テスト実行。

$ mocha test/functionals/HomeControllerTest.js

  .

  1 test complete (27 ms)

あれ、デバッグログが出ない・・・。
と、ここでようやく気がつきました。

検証処理を実際にやっている部分(self.assertの呼び出し)は、コールバック関数内に書かれてる。
つまり、テストの実行と、検証処理が非同期になってるのか。

何故ステータスコードを変な値にしたのに失敗しなかったかって言うと、end()の呼び出してすぐにテスト自体が終わってしまって、end()内で非同期に呼び出されているself.assertの結果が無視されているという感じかな。

MochaのドキュメントにAsynchronousがどーのこーのって書いてあった気がするぞ。
ドキュメントを読み直すと、itの第2引数の関数の引数として、コールバック関数を受け取れって書いてある。引数の名前はdoneにしておくのが普通だよって書いてあったのでそうしてみる。
そして、done()が呼び出されるまではMochaはテストを完了させないらしい!

var app = require('matador').createApp(__dirname + '/../../')
   ,should = require('should')
   ,request = require('supertest');
describe('HomeControllerのテスト', function() {
  it('その1', function(done) {
    request(app)              
      .get('/')
      .expect(2000)
      .end(function(err, res){
        if(err) throw err;
        done();
      });
  }
}


テスト実行。

$ mocha test/functionals/HomeControllerTest.js

  debug
  .

  × 1 of 1 test failed:

  1) HomeControllerのテスト その1:
     Error: expected 2000 "undefined", got 404 "Not Found"

おー!ちゃんとデバッグログも出たし、テストも失敗した!

けど、失敗の原因がおかしい。404?200が返ってくる想定なんだが・・・。
これはルーティングの設定が効いてないんだな。

いろいろググって調べる。

たぶん、appの作り方が悪い。
server.jsを見ると作成したappオブジェクトに対して何やら色々設定している。
server.jsで生成したappオブジェクトを利用できればいい。

module.exports に設定したオブジェクトは、requireでアクセスできるようになることが分かった。
テストからserver.jsのappを使えるように、server.jsにmodule.exports = app を追加。
あと、テスト時にポートを使わないようにするには、module.parentをチェックして制御するといいらしい。

module.exports = app;

if (!module.parent) {
    app.listen(port)
    console.log('matador running on port ' + port)
}                                                 

appへの代入部分を書き換える。

var app = require('../../server')
   ,should = require('should')
   ,request = require('supertest');
describe('HomeControllerのテスト', function() {
  it('その1', function(done) {
    request(app)              
      .get('/')
      .expect(2000)
      .end(function(err, res){
        if(err) throw err;
        done();
      });
  }
}


テスト実行。

$ mocha test/functionals/HomeControllerTest.js

  .

  × 1 of 1 test failed:

  1) HomeControllerのテスト その1:
     Error: expected 2000 "undefined", got 200 "OK"

おー!できた。

よかったよかった。


まとめ

  • supertestを使えばMatadorのエンドツーエンドテストができる
  • supertestは、テストの実行と検証が非同期に行われる
  • Mochaで非同期処理をテストする場合は、itの第2引数の関数で関数を受け取り、その関数をテストを終了させたい時に呼ぶ
  • modules.exports にappオブジェクトを設定すれば、テストからrequireでアクセスできる

サーバサイドJavaScript Node.js入門

サーバサイドJavaScript Node.js入門