GitHub Actions の使い捨て環境でマルチDBの Jest を実行する
はじめに
複数の DB に対応したアプリケーションを作っていると、 ちょっとした修正が SQL の方言や Knex のようなクエリビルダの仕様によって、不具合を起こすことがあります。
今、個人開発しているヘッドレス CMS も MySQL / PostgreSQL / SQLite から使う DB をユーザーが選択できるため、機能開発ではロジックだけでなく、それぞれの DB で正しく CRUD できることも確認する必要があります。
これはテストも同様で、1つのケースで複数の DB に接続して確認するなど、一工夫が必要になってきます。しかし、これらを CI で自動化できれば、思わぬバグに見舞われることも軽減できます。
今回は、GitHub Actions で、使い捨ての環境を立ち上げながら、複数の DB で Jest を実行する手順を紹介します。
紹介するコードはすべて GitHub で公開していますので、適宜ご参照くださいmm
環境
内容 | |
---|---|
テストツール | Jest |
DB | MySQL, PostgreSQL, SQLite...etc 何個でも! |
CI | GitHub Actions |
ゴール
Don't repeat 「MySQLはOK、ポスグレはエラー😱」
テストの流れ
- Docker を起動する
- Jest のセットアップでマイグレーション/seed を実行する
- 複数の DB でテスト実行!
Jest
コンフィグレーション
まずは、テストで使う DB の初期化を行います。 Jest コンフィグレーションファイルの globalSetup
にセットアップコードを指定します。
/** @type {import('ts-jest').JestConfigWithTsJest} */
export default {
...
globalSetup: './test/setups/setup.ts',
...
};
ここではテストする DB ごとにマイグレーションの実行と seed データを投入します。通常と違うのは、testDatabases
という配列で、これらを繰り返し行っているところです。
export default async (): Promise<void> => {
for (const testDatabase of testDatabases) {
const database = knex(config.knexConfig[testDatabase]!);
if (testDatabase === 'sqlite3') {
writeFileSync('test.db', '');
}
await database.migrate.latest();
await database.seed.run();
}
};
続いてテストコードです。
テストコード
こちらも同じく testDatabases
でイテレーションさせながら、後はいつものテストケースを書けばOK 🙆
describe('Users', () => {
describe('Create', () => {
it.each(testDatabases)('%s - should create', async (database) => {
const connection = databases.get(database)!;
const repository = new UsersRepository(tableName, { knex: connection });
const result = await repository.create(user);
expect(result).toBeTruthy();
});
});
});
1つ注意する点としては、afterAll
ですべてのコネクションを破棄することです。そうしないと、テストが Pass しているにも関わらず Jest がプロセスを終了せずに数分間待機してしまうことになります。
const databases = new Map<string, Knex>();
beforeAll(async () => {
for (const database of testDatabases) {
databases.set(database, knex(config.knexConfig[database]!));
}
});
afterAll(async () => {
for (const [_, connection] of databases) {
await connection.destroy();
}
});
テスト実行
以上で設定完了したので、さっそく実行してみましょう!
yarn test:int
こんな感じでテストが成功します~~やったね! 🙌
Run yarn test:int
yarn run v1.22.19
$ node --loader ts-node/esm --experimental-vm-modules node_modules/jest/bin/jest.js --config=jest.integrations.config.mjs
(node:2144) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use node --trace-warnings ...
to show where the warning was created)
Setup databases: [ 'sqlite3', 'mysql', 'maria', 'postgres' ]
🟢 Starting tests!
(node:2144) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
PASS test/api/repositories/projectSettings.test.ts (6.145 s)
PASS test/api/repositories/users.test.ts
-------------------------|---------|----------|---------|---------|------------------------------------------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---|---|---|---|---|---|
All files | 72.97 | 83.33 | 40 | 72.97 | |
src | 100 | 100 | 100 | 100 | |
env.ts | 100 | 100 | 100 | 100 | |
src/api/database | 39.74 | 100 | 0 | 39.74 | |
connection.ts | 39.74 | 100 | 0 | 39.74 | 32-78 |
src/api/repositories | 54.77 | 82.35 | 41.66 | 54.77 | |
base.ts | 87.91 | 70 | 54.54 | 87.91 | 31-35,77-78,85-86,89-90 |
projectSettings.ts | 72.22 | 100 | 50 | 72.22 | 13-17 |
users.ts | 29.54 | 100 | 27.27 | 29.54 | 13-17,25-30,40-49,52-63,66-81,84-107,110-116,119-131 |
src/exceptions | 68 | 100 | 33.33 | 68 | |
base.ts | 66.66 | 100 | 50 | 66.66 | 12-17 |
invalidCredentials.ts | 71.42 | 100 | 0 | 71.42 | 5-6 |
src/exceptions/database | 100 | 100 | 100 | 100 | |
recordNotUnique.ts | 100 | 100 | 100 | 100 | |
test | 96.15 | 100 | 0 | 96.15 | |
config.ts | 96.15 | 100 | 0 | 96.15 | 37-39 |
test/utilities | 100 | 50 | 100 | 100 | |
testDatabases.ts | 100 | 50 | 100 | 100 | 2 |
------------------------- | --------- | ---------- | --------- | --------- | ------------------------------------------------------ |
Test Suites: 2 passed, 2 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 8.422 s
Ran all test suites.
🏁 Tests complete!
今回は、すべて (SQLite / MySQL / Maria / PostgreSQL) の DB に対してテストを流しています。が、流石に毎度実行すると時間も CI のリソースも消費してしまいますよね。。そのときは以下のように、実行時にテストするものだけをパラメータとして渡して、省力化しておくとよいでしょう。
export const allDatabases = ['sqlite3', 'mysql', 'maria', 'postgres'];
export const testDatabases = process.env.TEST_DB?.split(',').map((v) => v.trim()) ?? allDatabases;
自分がつくっているヘッドレス CMS では、ロジックをいじったときは全ての DB それ以外は SQLite のみにしています。
TEST_DB=sqlite3 yarn test:int
GitHub Actions
無事ローカルでテストを実行できたので、GitHub Actions のワークフローとして登録していきましょう。繰り返しになりますが、テストを自動化しておくことで、ちょっとした修正や renovate によるライブラリのアップデートなどで、思わぬバグに見舞われることを軽減できます。
jobs:
integration:
name: ${{ matrix.database }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
database:
- sqlite3
- mysql
- maria
- postgres
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Yarn install
run: yarn install
- name: Start Services
if: matrix.database != 'sqlite3'
run: docker compose -f test/docker-compose.yml up ${{ matrix.database }} -d --quiet-pull --wait
- name: Run Tests
run: TEST_DB=${{ matrix.database }} yarn test:int
CI でも PR ごとにテストする DB を絞っていきます。
やり方はいくつかあるでしょうが paths
を指定して PR で修正したファイルがバックエンドコードだったり、テストコードそのものだったら全て走らせる条件を指定できます。
pull_request:
branches:
- main
paths:
- src/api/**
- test/api/**
- package.json
- .github/workflows/integration-full.yml
それ以外は、Docker の立ち上げが不要な SQLite だけで動かすなど時間と CI のリソースを節約していきましょう!
最後に
React / Node.js / RDB で動くオープンソースのヘッドレスCMS 『 Superfast 』 をつくっています 💪
マークダウンではGFM (GitHub Flavored Markdown) が使えたりダークモードに対応したり、エンジニアが書き心地のよいCMSを目指しています。Live Demo も公開していますので、ぜひ気軽に触ってみてください!
そして、気に入って頂けた方は、ワンラインでのローカル起動も試してみてください 😻
npx create-superfast-app my-app
それでは Happy Testing!!
Discussion