Express で構築した WebAPI サーバをユニットテストする (コードカバレッジも見る)
Express で構築した WebAPI サーバをユニットテストしたく、やり方を調べた。
目次
- 今回の要件
- テストランナーは Mocha を使う
- Express サーバの制御には supertest を使う
- HTML 形式でレポートを出力するために nyc を使う
- 任意 : Jasmine 風にテストを書けるように
expect
をインストールする - 以上
今回の要件
今回の前提、および達成したいことは以下のとおり。
- Node.js の Express を使って構築した RESTful な WebAPI サーバがある
- WebAPI サーバなので、常に JSON をレスポンスする。ファイルや Web ページを返すような箇所はない (画面を持たない)
- このプロジェクトでユニットテストを書きたい
- Angular を触っていたので、Jasmine みたいに書きたい
- ユニットテストによるコードカバレッジを集計したい
- Angular を触っていたので、Istanbul レポーターみたいな HTML 形式で見たい
Angular を触っていたので、Angular CLI による雛形プロジェクトが内包する、「Jasmine + Karma + Istanbul」な構成で、WebAPI サーバプロジェクトをテストしたいな、と思っている。
テストランナーは Mocha を使う
Angular アプリの場合は、テストを書くライブラリとして Jasmine があり、それを実行するテストランナーとして Karma が存在した。だが、Karma はブラウザ上でテストを行うテストランナーで、今回のように画面を持たない WebAPI サーバのユニットテストにはあまり適さないのだ。ということで Jasmine + Karma な構成は断念。
他のツールを調べてみると、Mocha というフレームワークを使う例が多かったので、コレを採用することにした。karma.conf.js
のような設定ファイルを作ることなく、ゼロコンフィグでユニットテスト環境が作れる。
# mocha をインストールする
$ npm install mocha --save-dev
# mocha はデフォルトでプロジェクト直下の test/ ディレクトリ内にある .js ファイルをテストコードとして認識するので
# まずは test/ ディレクトリを作る
$ mkdir test
# test/ ディレクトリ配下にテストコードを書くファイルを1つ試しに作る
$ touch test/my-test.js
test/my-test.js
の中身はこんな風に、describe()
や it()
でテストケースを別けて書ける。Jasmine っぽい。
describe('HOGE', () => {
it('FUGA', (done) => {
if('aaa' === 'aaa') {
done();
}
else {
done('失敗');
}
});
});
まずはテストファイルのみで、何の外部ファイルも読み込んでいないが、コレでテストが動くか確認してみる。
# ローカルインストールした mocha を実行するため npx を使用
$ npx mocha
コレでいきなりテストが実行され、コンソール上に結果が表示されるはずだ。終了する際は Ctrl + C
を押さないといけない様子。
Express サーバの制御には supertest を使う
次に、Express サーバを構成するコードを読み込んでテストをしたいので、Express サーバを制御するのに使える supertest という npm パッケージを使ってみる。
$ npm install supertest --save-dev
アプリ側のコードは以下のようにしておく。
const express = require('express');
const app = express();
// ルーティング定義などなど……
app.use('/', require('./routes/router'));
// UT 時はココが実行されないようにしておく
if(!module.parent) {
// サーバ起動
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Listen : ${port}`);
});
}
// 変数 app をエクスポートしておく
module.exports = app;
で、テストコード側は以下のように書く。
// 前述の Express サーバのコード
const app = require('../src/index');
// ココで listen() する
const supertest = require('supertest').agent(app.listen());
describe(`GET : '/'`, () => {
it('200 が返されるべきである', (done) => {
supertest.get('/')
.expect(200)
.end((error, response) => {
if(error) {
return done(error);
}
done();
});
});
});
アプリ側のコードで if(!module.parent)
と判定している部分が見慣れないだろうか。コレは、ユニットテストコード内で supertest が複数回 app.listen()
してしまうのを避けるために仕込んでいる。
この関係で、アプリ側のコードの if(!module.parent)
ブロック内はコードカバレッジが通らなくなることに留意。まぁココはサーバ起動のためのコードだからいいでしょ。w
とりあえずコレでテストが書けるようになった。
HTML 形式でレポートを出力するために nyc を使う
続いて、テスト結果を Istanbul の HTML 形式で出力したいので、nyc というパッケージを利用する。コレは Mocha の動作結果をパイプして、Istanbul レポータを出力したり仲介してくれるツール。
- 参考 : nyc - npm
コレをローカルインストールし、mocha
コマンドの手前に挟む。
$ npm install nyc --save-dev
package.json
で以下のように npm-scripts を組む。
"scripts": {
"test": "nyc --reporter=text --reporter=html mocha --watch"
}
このようにして、$ npm test
を実行すると、基本は mocha --watch
のとおり、ファイルの変更を監視する Watch モードで動作し、ひととおりテストが終わると、nyc が HTML 形式のレポートを ./coverage/
ディレクトリに出力してくれる。また、Ctrl + C
で終了すると、nyc がコンソールにレポートを出力してくれる。
任意 : Jasmine 風にテストを書けるように expect
をインストールする
ココまででやりたいことは達成できたが、テストを書く時の比較検証を Jasmine 風に書きたいので、expect というツールを入れることにする。
$ npm install expect --save-dev
そしてテストコードを以下のように修正する。
// Jasmine 風な expect() を書くため入れる
const expect = require('expect');
// Express サーバのコード
const app = require('../src/index');
// ココで listen() する
const supertest = require('supertest').agent(app.listen());
describe(`GET : '/'`, () => {
it('200 が返されるべきである', (done) => {
supertest.get('/')
.expect(200)
.end((error, response) => {
if(error) {
return done(error);
}
// Jasmine 風に expect() が書けるようになる
expect(response.text).toBe('Hello World');
done();
});
});
});
こんな感じで expect().toBe()
とかが書けるようになった。楽チン。
以上
ということで、
- Express で作った、画面を持たない WebAPI サーバをユニットテストするには
- mocha + supertest でテスト環境を構築し、
- mocha でテストした結果をカバレッジレポートに出力するには
- nyc をはさみ、
- ついでに Jasmine と同じように
expect()
を書くには- expect を入れる
という構成で解決できた。
今回作ったサンプルコードを含む、実際に動作するサンプルプロジェクトを以下に作ったので、コチラも参考にしていただければ幸い。