はじめに
この記事ではnode.js(Express)のAPIテストの第1弾として、テストを実行できるようになる部分までを記載している。
実際にテストを作る中でトラブって困った事もトラブった事として残している。
※第2弾としてmockを使ったより実践的なテストについても記事を書いてみたのでそちらも参照頂けると幸いです。
※第2弾は現在執筆中です。
どうやってExpressのAPI(End Point)テストをjestでやるのか?
Step1. jest, supertestをinstallする
Step2. nodeのテストを実行できるようにpackage.jsonを設定する
Step3. nodeのコード(Express)をテスト可能なようにリファクタリングする
Step.4 実際にテストを書く
Step1. jest, supertestをinstallする
npm
npm install --save-dev jest supertest
Step2. nodeのテストを実行できるようにpackage.jsonを設定する
package.json
{
・・・
"jest": {
"testEnvironment": "node",
"coveragePathIgnorePatterns": [
"/node_modules/"
]
},
"scripts": {
"test": "jest",
},
・・・
}
上記のjest configurationの意味について少し解説すると、
testEnvironmentjestのテスト実行環境を指定するkeyデフォルトではブラウザで動くjavascriptをテストする事が多いので、ブラウザのような環境としてjsdomが指定されるただ、node.jsの機能をテストする場合、nodeを指定してnodeのような環境でテスト実行するように指定する。1これはnodeでHTTP通信をしておいてAPIテストを行う場合、jsdomのままだとhttp requestがXHRになってしまい、CORSの制約などの細々した制約を受けてしまうので、それを避けるため。
coveragePathIgnorePatternsテストのカバレッジ計測対象外にするディレクトリを指定できるkey
ここまでできたらひとまずよくあるjestのテストの初めの一歩で出てくるテストを実際に書いてみて、npm run testで動くか?検証するとよい。
sample.test.js
describe('Sample Test', () => {
it('should test that true === true', () => {
expect(true).toBe(true)
})
})
こんな感じにPASSになればjestの設定はOK。
Step3. nodeのコード(Express)をテスト可能なようにリファクタリングする
まず、以下のようなnodeのソースコードはエラー2が発生してjestでテストできないので注意。
app.js
const express = require('express')
const app = express()
// Middlewares...
// Routes...
app.listen(8081)
詳細は以下のトラブった所の「ⅱ-applistenの部分でエラー」を参照してほしいが、
簡単に言うとlistenしたportが開きっぱなしになりそれを閉じてやることができないのでずっとserverが起動した状態になりjestのテストが終了せず、以下のようなエラーが発生しテストが失敗する。
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPSERVERWRAP
25 |
26 | // Setup Server
> 27 | app.listen(8081, function () {
| ^
28 | console.log('listening on port 8081!')
29 | })
30 |
at Function.listen (node_modules/express/lib/application.js:618:24)
at Object.<anonymous> (src/server/server.js:27:5)
ではどうすればいいかだが、以下のように単純にファイルを分割してあげればいい。
Express serverの起動方法は、
before
after
node app.js
node server.js
のように変わるだけ。
app.js
const express = require('express')
const app = express()
// Middlewares...
// Routes...
module.exports = app;
server.js
const app = require('./app')
app.listen(8081, () => {
console.log('listening on port 8081!')
});
Step.4 実際にテストを書く
ここまで出来たら実際にテストコードを書いてAPIテストを実行してみる。
テスト対象のコードは以下。
app.js
const express = require('express');
const app = express();
/* Middleware */
app.use(express.urlencoded({
extended: false
}));
app.use(express.json());
// Cors for cross origin allowance
const cors = require('cors');
app.use(cors());
// Initialize the main project folder
app.use(express.static('dist'))
// dotenv
const dotenv = require('dotenv')
dotenv.config();
const axios = require('axios').default;
// instance for get countries and cities
const instance = axios.create({
baseURL: 'https://api.countrystatecity.in/v1/',
timeout: 2000,
headers: { 'X-CSCAPI-KEY': `${process.env.COUNTRYSTATECITY_API_KRY}` }
})
app.get('/allCountries', async (req, res) => {
try {
const countries = await instance.get('countries')
res.send({ countries: countries.data })
} catch (error) {
errorHandler(res, error)
}
})
app.get('/allCitiesByCountry', async (req, res) => {
try {
const cities = await instance.get(`countries/${req.query.ciso}/cities`)
res.send({ cities: cities.data })
} catch (error) {
errorHandler(res, error)
}
})
const errorHandler = (res, error) => {
if (error.response) {
res.status(500).send(error.response.data)
} else {
res.status(500).send({ error: error.message })
}
}
module.exports = app;
テストコードは以下。
routes.test.js
const request = require('supertest')
const app = require('../../src/server/app')
describe('Get Endpoints (not mocking)', () => {
it('/allCountries', async () => {
const res = await request(app).get('/allCountries')
expect(res.status).toEqual(200)
expect(res.body.countries[0].name).toEqual('Afghanistan')
})
it('/allCitiesByCountry', async () => {
const res = await request(app).get('/allCitiesByCountry?ciso=JP')
expect(res.status).toEqual(200)
expect(res.body.cities[0].name).toEqual('Abashiri')
})
})
jestのテスト実行結果は以下のようになり、ちゃんとテストできている事が分かる。
ただこのままではテストしてよくない・・・
上記のように実際にWeb APIをCallしてのテストは安定せず非推奨。
そこでmockの機能を使って通信をmock化する事でより実践的な(CI上で継続的にテストする)テストを書いてみる。
続きはJest・Supertestを使用したnode.js(Express)のAPIテスト 実践編を参照。
実際にmock化せず何回かテストをすると、成功する時もあれば失敗する時もあり安定しない。
また、今回はapp.jsにEnd Pointを記載していたが、routes・conrollersという構成がきれいで一般的なのでそちらのコードにリファクタリングしたものもJest・Supertestを使用したnode.js(Express)のAPIテスト 実践編で扱う。
トラブった事
ⅰ. ReferenceError: regeneratorRuntime is not defined
webpackでbabelを使用しているプロジェクトだったが、asyncを使用したコードをjestでテストをした時に、ReferenceError: regeneratorRuntime is not definedというエラーが発生した。
これは.babelrcにて、babelのターゲットを指定することで解決できる3。
.babelrc
{
"presets": [
[
"@babel/preset-env", {
"targets": {
"node": "current"
}
}
]
]
}
ⅱ. app.listen('・・・')の部分でエラー
上記のStep3. nodeのコード(Express)を・・・で少し触れたが、以下のソースコードはエラーになる。
app.js
const express = require('express')
const app = express()
// Middlewares...
// Routes...
app.listen(8081)
ひとまずどうなるのか?を詳しく見ていくためにわざとこのままjestのテストを実行してみると、
のようにエラーになる。
logに出ている内容としては色々あるが、
A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks.ワーカープロセスが正常に終了できず、強制的に終了しました。 これは不適切な分解によるテストのリークが原因である可能性があります。 --detectOpenHandlesを指定して実行し、リークを見つけてください。
と言っているので、jest --detectOpenHandlesを今度は実行してみる。すると、
のように、jestのテストが終了せずずっと実行されたままの状態になってしまう。
Jest has detected the following 1 open handle potentially keeping Jest from exiting:Jestは、次の1つの開いたハンドルを検出したため、Jestが終了しない可能性があります。
と言っているようにjestのプロセスが終了しないためエラーになっており、これはapp.listen(8081)でserverを起動してしまいそれがずっと生きている事が原因。なのでStep3. nodeのコード(Express)を・・・で書いたようにapp.jsとserver.jsとに分割する必要がある。
※ここで実行していたテストコードは以下。
routes.test.js
const request = require('supertest')
const app = require('../../src/server/app')
describe('Get Endpoints', () => {
it('/allCountries', async () => {
const res = await request(app).get('/allCountries')
expect(res.status).toEqual(200)
})
})
実際にテストを書いたプロジェクト
参考文献
Jest公式
asyncを使用したコードをjestでテストすると「regeneratorRuntime is not defined」エラーが発生
jest で Node.js の テストするなら testEnvironment: "node" を使う
Testing NodeJs/Express API with Jest and Supertest
Testing NodeJs/Express API with Jest and Super test
Endpoint testing with Jest and Supertest
https://jestjs.io/ja/docs/configuration#testenvironment-stringhttps://qiita.com/yosuke_furukawa/items/8c14b7b4461cc109bc54ただ、const cors = require('cors'); app.use(cors());を使っていればCORSの問題は発生しない。 ↩
トラブった所② ↩
https://qiita.com/quzq/items/1662bff594f05d52f206 ↩
↧