Apollo GraphQLに入門してみた(おまけ)、リファクタリング・テスティングに書いたApollo GraphQLサーバーの学習をかねたプロトタイプ作成が完了し、それを基に本番サーバーを作りました。
しかし、テスティングの部分でいくつか不足点が見つかりコードを追加したりしました。
テスティング環境
Apollo GraphQLに入門してみた(おまけ)、リファクタリング・テスティングにも書きましたがテスティング環境は、
- テスティングフレームワークとしてはReactでは標準的なJestを使用
- サーバーはExpressの上で動いているので、Expressの豊富なライブラリーが利用できます。今回はSuperTestというExpress用のテストライブラリーを使っています
- SuperTestはExpressを独立したサーバー起動はせず、SuperTest上でExpressのコードを動かします。テストコードからは内蔵されているHTTPクライアント・ライブラリーsuperagentを使ってテストを書きます
テストコードの例
import { beforeEach, afterAll, describe, expect, it } from '@jest/globals'
import request from 'supertest'
import app from '../../src/server'
import { getSidCookie, setupDatabase, cleanupDatabase } from '../helper'
beforeEach(setupDatabase)
afterAll(cleanupDatabase)
describe("Mutation: userLogin(email, password)", () => {
describe("正しいemail,passwordの場合", () => {
let res: request.Response
beforeEach(async() => {
res = await request(app).post("/graphql").send({
query: `mutation {
userLogin(email: "yama@mail.com", password: "test@123") {
id name email
}}`
})
})
it("成功し、ユーザー情報が戻る", () => {
expect(res.status).toBe(200)
const userLogin = res.body.data.userLogin
expect(userLogin.email).toBe("yama@mail.com")
expect(userLogin.name).toBe("ユーザ1")
})
it("成功した場合はCookieにSessionIDが入っている", () => {
expect(res.status).toBe(200)
expect((getSidCookie(res)).length).toBeGreaterThan(10)
})
})
・・・・
})
テスティング用データーベース
さて、Apollo GraphQLに入門してみた(おまけ)、リファクタリング・テスティングに書いた時点ではいくつかの点を無視していました。
以前は、Ruby on Railsを使っていましたが、Railsにはテスト駆動開発を行いやすいように、いろいろな機能がRailsやrspec-railsに内蔵されていました。
テスト用データーベース
テスティング用のデーターベースは開発用と同じでもなんとかなるかもしれませんが、テスト専用のデーターベースがあると便利です。docker-composeを使っているのでデータベース名は環境変数でサーバーに渡しているので、テスト時はそれを変更すれば良いので、docker-composeの設定ファイルを複数指定することでテスト実行ができます。
- テストの実行
docker-compose -f docker-compose.yml -f dc.test.yml up
- dc.test.yml
version: "3.7"
services:
server:
environment:
- DB_DATABASE=digimagi_test
command: bash -c "
./tools/wait-for-it.sh localhost:5432 -- npm run migrate && npm test"
テストデータ
テストを書くには、テスト開始時にいつも同じテストデータから始められるとテストが書きやすくなります。Railsにはテストデータを簡単に準備できるFixtureが組み込まれています、またfactory_botのようなテストデータ作成ライブラリーもあります。
node.jsにも似たものはあるようですが、今回は専用のSQLを書いて済ましました。
また、データベースの接続ハンドラーはApollo GraphQLサーバーのコードから取得しています。SuperTestはテストコード上でサーバーのコードが動くのでデータベース接続ハンドラーは共有できます。
import app, { db } from '../src/server'
export const setupDatabase = async () => {
await db.none("BEGIN")
await db.none("TRUNCATE TABLE session,works,users")
await db.none("ALTER SEQUENCE users_id_seq RESTART")
await db.none("ALTER SEQUENCE works_id_seq RESTART")
await db.none(`INSERT INTO users(name,email,language,password) VALUES('山田',
'yama@mail.com', '日本語', '*******************************')`)
await db.none(`INSERT INTO users(name,email,language,password) VALUES('川田',
'kawada@mail.com', '日本語', '*******************************')`)
await db.none(`INSERT INTO works(name,user_id,all_data,created_at) VALUES('作品1',
1, '{"a": 11, "b": 21, "c": {"x": 10, "y": 20}}', '2022-03-17T23:18:00Z')`)
await db.none(`INSERT INTO works(name,user_id,all_data,created_at) VALUES('作品2',
1, '{"a": 12, "b": 22, "c": {"x": 10, "y": 20}}', now())`)
await db.none(`INSERT INTO works(name,user_id,all_data,created_at) VALUES('作品3',
2, '{"a": 13, "b": 23, "c": {"x": 10, "y": 20}}', now())`)
await db.none("COMMIT")
}
export const cleanupDatabase = async () => {
await db.$pool.end()
}
ときどきテストが失敗する
さて、npm test
を行うと時々テストが失敗します。しかしテストファイルを1つだけ指定すると成功します。😅
ログを見ていると、複数のテストファイルが同時に実行されているようです。Jestコマンドのドキュメント を調べてみると--maxWorkers
というオプションがありました。
デフォルトはCPUコア数の50%なので、私の使っているMacでは3です。
したがって、場合によっては3個のテストファイルが同時に実行されます。同時にテストコードが実行されるとデータベース操作が重なり、思わぬエラーが発生してしまいます。
解決方法は、--maxWorkers=1
を指定してテストが複数同時実行されないようにします。テスト時間は長くなりますが、現在のところテスト時間はまったく問題ありません。