こんにちは、江嵜です。
みなさん、テスト書いてますか?
ぶっちゃけ私はテスト書くのあんまり好きではありません。
なんというか、作業感あるんですよね。テストって。
まぁ、そんなこと言っても、実際テストは必要ですし、
何度もテストに助けられているので、書くんですけど。
今回は、express で作っている API に対して、どうやったら簡単にテストをかけるかなと思って考えてみました。
大体こういうの調べると、実際に express 動かして superagent でリクエスト流して…
みたいなことしていますが、それは面倒なので、今回は superagent なしでやってみようかと。
とりあえずテストされる環境準備
今回は express と jest を使うので、この二つを準備しましょう。
ちなみに jest は
Jest is a delightful JavaScript Testing Framework with a focus on simplicity.
と公式ページにも書かれていますが、
とても簡単にテストが書けるフレームワークです。
npm install --save express npm install --save-dev express
サーバーのコードはとりあえず簡単にこんな感じで。
const express = require(’express’) const app = express() const router = express.Router() router.get(’/’, (req, res) => { if (req.query.id) { return res.status(200).send(`Hi! ${req.query.id}`) } res.status(400).send(’Who are you?’) }) app.use(’/’, router) app.listen(3000)
サーバーを node index.js
で起動して、 Postman でリクエスト叩いてみると…
きちんと id に設定した名前で返答してくれています。
問題なさそうですね。
jest でテストを書く
では、このサーバーに対してテストを書きましょう。
ここで最終的にテストしたいのは、一言で言えば
「 /
(ルート) にアクセスしたときに、 id が含まれていれば名前を含む文字列を返す」
ということでしょうが、これはいわゆる結合テストですね。
この場合は実際に起動したサーバーに対して superagent などでリクエストするしかないですが、
基本はもっと細かい粒度でやりたいところかなと思います。
ということで、ルーティングは外して、
アクセスされたときの挙動 (「id が含まれていれば名前を含む文字列を返す」 の部分) だけテストしてみましょう。
ルーティングと処理を切り離す
まずは最初に、ルーティングの部分と、実際の処理を切り離す必要があります。
JavaScript なら簡単に別ファイルに切り出せますね。
index.js
const express = require(’express’) const app = express() const router = express.Router() const { Get } = require(’./root’) router.get(’/’, Get) app.use(’/’, router) app.listen(3000)
root.js
exports.Get = (req, res) => { if (req.query.id) { return res.status(200).send(`Hi! ${req.query.id}`) } res.status(400).send(’Who are you?’) }
処理の方を、root へのリクエストなので root.js
として切り出しました。
これで、 root.js
の Get
に対してテストを書くことで
ルーティング部分を無視してテストができますね。
本当はもう一つ理由があるのですが、それは後ほど。
テストを書く
では実際にテストを書いていきましょう。
今回は最初にお話した通り、 jest を使っていきますよ。
__test__
ディレクトリを作成して、その中にテストを格納します。
root.test.js
const { Get } = require(’../root’) test(’status 200 with id’, () => { // ここにテストを書く })
こんな感じですね。
さて、ここでどのようにテストを書くか、という事になります。
とりあえず id が格納されてれば 200 のステータスコードが格納されることを確認したいですね。
今回はサーバーを実際に立てるわけではないので、リクエストをして
ステータスコードを確認することはできません。
ここで、元の root.js を確認してみると、
引数から渡された res オブジェクトの status()
に渡された値がステータスコードですね。
ということで、これを確認して、 200 が引数として渡されたことを確認できればよい、ということです。
さて、ここで先ほど、処理をルーティングから分けたもう一つの利点が発揮されます。
処理を分けたことで、今回テストする部分は単純な関数になっていますね!
ということで、 req, res に当たるオブジェクトのモックを作って、
単純に関数に渡すだけでテストが出来る!という寸法ですね。
そして、 jest には強力なモック作成機能がありますので、
これを使えば…
const { Get } = require(’../root’) test(’status 200 with id’, () => { const req = { query: { id: ’ezaki’ } } const res = { status: jest.fn().mockReturnThis(), send: jest.fn().mockReturnThis() } Get(req, res) expect(res.status.mock.calls[0][0]).toBe(200) })
request の方は、単純に query に格納されたオブジェクトを参照しているだけなので、カンタンですね。
response の方は jest.fn()
を使ってモックを作成しています。
mockReturnThis()
を指定しておけば、 this を返却してくれるので、
express のように res.status().send()
といった感じでチェーンメソッドで
プログラムを書くことにも簡単に対応できます。
で、実際のテストは
expect(res.status.mock.calls[0][0]).toBe(200)
ここですね。
詳しい説明は公式ドキュメントを参照してもらうとして、
カンタンに話すと、呼び出されたモックに対して mock.calls[n][m]
で、
- n 番目の引数に対して
- m 回目の呼び出しで
指定された引数を取り出します。
今回は、引数 1 つ、呼び出した回数も 1 回なので、 mock.calls[0][0]
に
ステータスコードとして指定した 200 が格納されているのですね。
と、こんな感じでテストを書いて、 package.json の scripts に
”test”: ”jest”
として、 npm test
を実行すると…
バッチリですね!
ちなみに request の id を空にしてみると…
きちんとテスト失敗していますね。
JavaScript でもサクサクテストかこう!
いかがでしょうか。
express のサーバー起動して…とかってテストすると大変ですが、
切り分ければ結構簡単にテストかけるんだなーって感じですよね。
この簡単さは jest のモック機能によるところが大きいので、
jest の便利さも分かってもらえたんじゃないかなと思います。
それではみなさん!
JavaScript でも楽しいテスト生活しましょうね!