🙌

NestJS+Prisma+Jestで実際のDBMSを使用した自動テスト

2024/02/07に公開

経緯

実際のDBMSを用いたテストを書く際に、ベストプラクティス的なものが見つからず試行錯誤。
そんな中で自分なりの結論が出たため、同じ悩みを持った誰かの引き出しになればと思い記事を書きました。

前提

  • NestJS
  • Typescript
  • Prisma (schema.prismaがありnpx prisma db pushが通るようになっていること)
  • postgres (多分MySQLでも問題ないが、ここではpostgres)

この記事のゴール

  • テスト毎にデータが空になる(出来る)こと
  • GitHub Actionsで実行できること
  • Jestを並列で実行できること
  • npm run testで実行できること

下準備

Docker

docker-compose.yml

version: '3.9'
services:
  db:
    image: postgres:15.3
    restart: always
    container_name: integration-tests
    ports:
      - "5400:5432" # 競合を避けて5400としています
    environment:
      POSTGRES_DB: "tests"
      POSTGRES_USER: "prisma"
      POSTGRES_PASSWORD: "prisma"

package.json

package.jsontestはDockerが立ち上がるよう以下のようにします。

{
  ...
  "scripts": {
    ...
    "test": "docker-compose up -d && jest && docker-compose down",
  },
}

環境変数

自分の場合は.env.testというtest用のenvファイルを作成しましたが、
各々の環境に合わせ追加してください。

# Local DB
DATABASE_URL="postgresql://prisma:prisma@localhost:5400/tests"

ヘルパー関数の実装

import { PrismaClient } from '@prisma/client';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
import { execSync } from 'child_process';

/**
 * JEST_WORKDER_ID毎にDatabaseを作成し、データのリセット処理を行う。
 */
export async function setupDatabase() {
  // 作成するDB名
  const newDbName = `worker_${process.env.JEST_WORKER_ID}`;

  // DBの作成
  const prisma = new PrismaClient();
  await prisma.$connect();
  try {
    await prisma.$executeRaw`CREATE DATABASE ${newDbName}`;
  } catch (error) {
    if (error instanceof PrismaClientKnownRequestError) {
      // DB作成済みだった場合は無視
      // 本来はここでエラーコードをチェックした方が良い。今回は割愛
    } else {
      throw error;
    }
  }
  await prisma.$disconnect();

  // 環境変数上書き
  const dbUrl = new URL(process.env.DATABASE_URL ?? '');
  const baseUrl = dbUrl.href.substring(0, dbUrl.href.lastIndexOf('/'));
  process.env.DATABASE_URL = `${baseUrl}/${newDbName}`;

  // DB初期化処理
  execSync('npx prisma migrate reset --force --skip-seed', {
    env: {
      ...process.env,
    },
  });
  execSync('npx prisma db push', {
    env: {
      ...process.env,
    },
  });
}

これでsetupDatabase関数を呼び出すことで任意のタイミングでDBをリセット出来るようになりました。

execSyncを使用した初期化やJEST_WORKER_ID毎にDBを作るというアイデアはこちらの記事を参考にしています。
https://www.mizdra.net/entry/2022/11/24/153459

使用例

beforeAllで一回だけ初期化

describe('UserService Integration Test', () => {
    let service: UserService;
    let prisma: PrismaService;

    beforeAll(async () => {
        // 最初の一回だけ初期化
        await setupDatabase();

        const module: TestingModule = await Test.createTestingModule({
            providers: [
                UserService,
                PrismaService,
                LoggingService,
            ],
        }).compile();

        service = module.get<UserService>(UserService);
        prisma = module.get<PrismaService>(PrismaService);

        // データベースにテスト用データの挿入
        await prisma.userEntity.create({
            data: {
                id: 1,
                name: 'TEST_USER'
            },
        });
    });
}, 1000); // setupDatabaseの時間がかかる場合はこのようにタイムアウト時間を伸ばしてください

it('ユーザーを取得できること', async () => {
    const user = await service.getUser(1);// DBからID:1を取得する処理
    expect(user.id).toBe(1);
    expect(user.name).toBe('TEST_USER');
});

jest.setup.tsで自動的に初期化

jest.setup.tsで宣言すればテスト毎に呼び出す必要がなくなります。

jest.config.ts

module.exports = {
    ...,
    setupFilesAfterEnv: ['jest.setup.ts']
}

jest.setup.ts

beforeAll(async () => {
    await setupDatabase();
}

ただ、自分の環境ではDBに関連しない関数のテストも含まれていた為、jest.setup.tsではなくテスト毎に呼び出す方式を採用しました。

GitHub Actions (workflow)

name: Integration Test
on: pull_request
jobs:
  tests:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: npm

      - name: NPM install
        run: npm install

      - name: TSC
        run: tsc

      - name: Run Test
        run: npm run test

最後に

setupDatabaseはどうしても時間がかかってしまうのが気になりポイントですね。

DBの初期化周りはもっといい方法あると思っていますが、一旦諦めた感じです。

もっといい方法あるよ!って方がいたらコメントで教えて頂けると幸いです。

GitHubで編集を提案

Discussion