🍰
Prismaで快適にテストを行なうヘルパーを考えた
はじめに
Prisma: 3.3.0 // 執筆時に使用した Prisma のバージョン
PostgreSQL
データベースを利用したテストは個人的に mock でなく実際に読み書きしたい派です。
となると出てくる問題として、データベースの状態をリセットする方法です。
データベースのお掃除
記事等を探していると以下の方法が見つかりました。
見つけたリセット方法 1
// 参考リンクから抜粋
export const cleanupDatabase = async (): Promise<void> => {
const prisma = new PrismaClient();
const modelNames = Prisma.dmmf.datamodel.models.map((model) => model.name);
await Promise.all(
modelNames.map((modelName) => prisma[modelName.toLowerCase()].deleteMany())
);
prisma.$disconnect();
};
以下のリンクで見つけたのは、PrismaClient の deleteMany で削除する方法です。
メリット
-
deleteMany
を呼ぶだけなので安全な操作
デメリット
- relation があるときにうまく動作しない
- Prisma は relation を追加するとき
RESTRECT
で生成するためCASCADE
で削除してくれない - 枝の先から削除していく必要がある
- Prisma は relation を追加するとき
見つけたリセット方法 2
// 参考リンクから抜粋
import util from "util";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const exec = util.promisify(require("child_process").exec);
export const resetDatabase = async (): Promise<void> => {
const prismaBinary = "./node_modules/.bin/prisma";
await exec(`${prismaBinary} migrate reset --force`);
};
1 と同じ方のスクラップにあった別の方法です
メリット
-
prisma migrate reset
を実行するのでDROP TABLE
→CREATE TABLE
→seed を行なってくれる - 全てのデータを初期状態にできる
- id を自動採番にしている場合、テーブルの採番が 1 からにリセットされる
デメリット
- 実行が遅い
- ファイル毎に実行していたら 1 ファイルあたり 3 秒~かかるのでしんどかった
見つけたリセット方法 3
// 参考リンクから抜粋
const tablenames =
await prisma.$queryRaw<Array<{ tablename: string }>>`SELECT tablename FROM pg_tables WHERE schemaname='public'`
for (const { tablename } of tablenames) {
if (tablename !== '_prisma_migrations') {
try {
await prisma.$executeRawUnsafe(`TRUNCATE TABLE "public"."${tablename}" CASCADE;`)
} catch (error) {
console.log({ error })
}
}
公式ドキュメントで全てのデータをリセットするための例として書かれていました。
メリット
-
CASCADE
デリートしてくれるため全て削除可能
デメリット
- id を自動採番にしている場合、テーブルの採番が 1 からにリセットされない
ベストプラクティス
import { PrismaClient } from "@prisma/client";
import { Prisma } from ".prisma/client";
export const resetTable = async (
modelNames: Prisma.ModelName[]
): Promise<void> => {
const tablenames = modelNames.map((modelName) => ({ tablename: modelName }));
for (const { tablename } of tablenames) {
try {
await prisma.$executeRawUnsafe(
`TRUNCATE TABLE "public"."${tablename}" RESTART IDENTITY CASCADE;`
);
} catch (error) {
console.log({ error });
}
}
};
テスト向けに以下を反映しました。
- 引数で削除したいテーブルを指定可能
✔beforeEach
などで使うことで拡張性アップ -
RESTART IDENTITY
を指定して自動採番をリセット
デメリット
- seed したデータを消すと復活しない
- 消したら流し直す必要あり
デメリット解消方法
cleanup の処理をまとめるヘルパーを作成しました。
テストファイルの先頭で関数を実行することでbeforeAll
, afterAll
でいい感じにデータベースのリセットと seed の代わりとなる処理を行なえます。
// seedの対象
// - adminにTEST_ADMINS
// - userにTEST_USERS
export const cleanup = (modelNames: Prisma.ModelName[] = []) => {
beforeAll(async () => {
// seed対象のテーブルは常にリセット
await resetTable(["Admin", "User", ...modelNames]);
// 以下でseed
await prisma.admin.createMany({ data: TEST_ADMINS });
await prisma.user.createMany({ data: TEST_USERS });
});
afterAll(async () => {
await resetTable(["Admin", "User", ...modelNames]);
await prisma.$disconnect();
});
};
おわりに
Prisma便利だァ...
Discussion