Testcontainersを利用し実際のデータベースを用いたテストを並列実行する

2025 / 01 / 20

Edit

自分はDBをモックしてテストを行うのが嫌いですが、もし実際のDBを利用し並列実行する場合にポートやtruncate周りの問題が発生します。 今回は、それを解決するためにTestcontainersとVitestを利用したコードで解決します。

Testcontiners

Docker社が買収したTestcontainersは、Dockerコンテナをテストで利用しやすくするライブラリです。 開発時にdocker composeを利用している人が多いと思うので、そこで利用しているcompose.yamlの定義をまんまテストで再利用できる点も便利かなと思います。

Testcontainers Testcontainers is an opensource library for providing lightweight, throwaway instances of common dat...

ポート衝突の問題

一般的に開発時は固定のポートを利用するため、テストを並列で実行する場合にポートが衝突する問題が発生します。

なので、${DATABASE_PORT:-5432}:5432と書くことによりDATABASE_PORTという環境変数を尊重し、ない場合はデフォルトのポートを参照するように変更します。 これにより、普段の開発時にはDATABASE_PORTを指定せず、テスト実行時だけそれぞれのテストスイートが実行時に指定できるようになります。

services:
  db:
    image: postgres:17
    ports:
      - ${DATABASE_PORT:-5432}:5432
    environment:
      - POSTGRES_USER=${DATABASE_USER}
      - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
      - POSTGRES_DB=${DATABASE_DB}

この環境変数をTestcontainersでコンテナ起動時に上書きすることによりポートを動的に切り替えます。

// https://github.com/hiroppy/web-app-template/blob/main/tests/db.setup.ts

import { exec } from "node:child_process";
import { promisify } from "node:util";
import { Prisma, PrismaClient } from "@prisma/client";
import { DockerComposeEnvironment, Wait } from "testcontainers";
import { createDBUrl } from "../src/app/_utils/db";

const execAsync = promisify(exec);

const container = await new DockerComposeEnvironment(".", "compose.yml")
  .withEnvironmentFile(".env.test")
  .withEnvironment({
    DATABASE_PORT: port === "random" ? "0" : `${port}`, // 環境変数を差し込み、ポートを動的に変更
  })
  .withWaitStrategy("db", Wait.forListeningPorts())
  .up(["db"]);
const dbContainer = container.getContainer("db-1");
const mappedPort = dbContainer.getMappedPort(5432);
const url = createDBUrl({
  host: dbContainer.getHost(),
  port: mappedPort,
});

// migration
await execAsync(`DATABASE_URL=${url} npx prisma db push`);

const prisma = new PrismaClient({
  datasources: {
    db: {
      url,
    },
  },
});

これにより、prismaのクライアント自体もURLを切り替えた状態で作成でき、テスト内ではこのクライアントを利用します。 そしてそれぞれのテストスイート事にDBが隔離され、安全に並列実行できるようになります。


コード一覧は以下のページやリポジトリを参照。

Unit Testing | Web App Template From Zero to Service: Build with Best Practices, Minimal Code, and Essential Tools.

GitHub - hiroppy/web-app-template: A minimal template for web app including DataBase, Google Auth, and frontend infrastructure 🎃 A minimal template for web app including DataBase, Google Auth, and frontend infrastructure 🎃 - hir...