Skip to content

test : DB結合テスト基盤の導入とrunInTransactionテストの移行#129

Open
Mel-906 wants to merge 5 commits intofeature/uow-repository-transactionfrom
feature/integration-test-infrastructure
Open

test : DB結合テスト基盤の導入とrunInTransactionテストの移行#129
Mel-906 wants to merge 5 commits intofeature/uow-repository-transactionfrom
feature/integration-test-infrastructure

Conversation

@Mel-906
Copy link
Copy Markdown

@Mel-906 Mel-906 commented Mar 30, 2026

Summary

  • DB結合テスト基盤を構築(Docker Compose + Vitest設定分離 + テストヘルパー)
  • 既存のrunInTransaction.test.tsを新基盤に移行
  • CIにintegrationテストジョブを追加

背景

プロジェクト初のDB結合テスト基盤です。
今後、他のメンバーがRepositoryやUseCaseの結合テストを追加する際の土台になることを意識しています。
具体的には、テストファイルを *.integration.test.ts で作成するだけで、DB接続・スキーマ同期・テストごとのクリーンアップが自動で行われる構成にしています。

変更内容

1. テスト用DB(Docker Compose)

  • docker-compose.test.yml を追加
  • PostgreSQL 17 (Alpine)、ポート5433、tmpfs(RAM上で高速)
  • ヘルスチェック付きで --wait に対応

2. テストヘルパー(tests/helpers/

  • db.ts: テスト用DB接続・Drizzleクライアント・スキーマからの動的テーブル列挙によるTRUNCATE CASCADE・マイグレーション(実行前にDB名ガード付き)
  • globalSetup.ts: テストスイート開始時にDBスキーマを同期(drizzle-kit push)
  • setupIntegration.ts: 各テスト前にDATABASE_URL設定 + 全テーブルTRUNCATE

3. Vitest設定分離

  • vite.config.integration.ts を追加(*.integration.test.ts のみ対象)
  • vite.config.tsexclude を追加し、unitテストにintegrationが混入しない構成

4. npmスクリプト

  • test:integration — 結合テスト実行(DB起動済み前提)
  • test:integration:up — DB起動 → テスト実行(ワンコマンド)
  • test:integration:down — テスト用DBコンテナ停止

5. CI

  • test-integration ジョブを追加(GitHub Actions servicesでPostgreSQLを起動)

6. テスト移行

  • runInTransaction.test.tsrunInTransaction.integration.test.ts
  • 個別のDB接続管理・手動DELETE を廃止し、共通ヘルパーに統一

テストで確認している事柄(runInTransaction)

テストケース 確認内容
複数の書き込みがすべて永続化される トランザクション内で2件INSERTし、コミット後にDBから両方取得できることを確認
途中で例外が発生したら全て巻き戻る INSERT後に例外を投げ、ロールバックによりレコードが残っていないことを確認
ネスト時に外側の失敗で全体が巻き戻る 内側のrunInTransactionでINSERT成功後、外側で例外を投げ、内側の書き込みも含めて全て巻き戻ることを確認(ネストが新しいトランザクションを開始せず、外側に合流する動作の検証)

結合テストの追加方法(今後のために)

  1. *.integration.test.ts でファイルを作る
  2. アサーション用DBクライアントは import { getTestClient } from "../../helpers/db" で取得
  3. クリーンアップは自動(beforeEachでTRUNCATE)
  4. ローカル実行: npm run test:integration:up

Test plan

  • npm run test:integration:up で3テスト全パス
  • npm test で既存127テスト全パス(integrationが混入していないことを確認)
  • CIでの test-integration ジョブの動作確認

🤖 Generated with Claude Code


Open with Devin

@Mel-906 Mel-906 requested a review from KinjiKawaguchi March 30, 2026 16:38
devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Member

@KinjiKawaguchi KinjiKawaguchi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Titleを test:hogehoge
にしたほうが良さそう。

Copy link
Copy Markdown
Member

@KinjiKawaguchi KinjiKawaguchi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DB接続情報がテストコード、Docker Compose、CIの3箇所に散在しているのが気になります。

.env.testPOSTGRES_* を一元管理し、Docker Composeとテストコードの両方がそこから読むようにすると解消できます:

.env.test:

POSTGRES_USER=core_test
POSTGRES_PASSWORD=core_test
POSTGRES_DB=core_test
POSTGRES_PORT=5433

docker-compose.test.yml:

services:
  test-db:
    image: postgres:17-alpine
    env_file: .env.test
    ports:
      - "${POSTGRES_PORT:-5433}:5432"
    # ...

tests/helpers/db.ts:

import "dotenv/config"; // .env.testをロード

const user = process.env.POSTGRES_USER;
const password = process.env.POSTGRES_PASSWORD;
const db = process.env.POSTGRES_DB;
const port = process.env.POSTGRES_PORT ?? "5433";
export const TEST_DATABASE_URL = `postgresql://${user}:${password}@localhost:${port}/${db}`;

のようにすることで接続情報のソースが .env.test の1箇所に集約されると思います。


これはアドバイスなんですが、process.env は型なし・グローバル・ミュータブルな外部依存です。プロダクションコードでDB接続をRepository境界に閉じ込めるのと同じ考え方で、process.env への直接アクセスも境界層に閉じ込めて、内側のコードには型のついた値として渡すのが望ましいです。

具体的には、process.env を読む専用の設定モジュールを用意します:

tests/helpers/config.ts:

import "dotenv/config";

export const testConfig = {
    db: {
        user: process.env.POSTGRES_USER ?? "core_test",
        password: process.env.POSTGRES_PASSWORD ?? "core_test",
        name: process.env.POSTGRES_DB ?? "core_test",
        port: Number(process.env.POSTGRES_PORT ?? 5433),
    },
} as const;

export const TEST_DATABASE_URL =
    `postgresql://${testConfig.db.user}:${testConfig.db.password}@localhost:${testConfig.db.port}/${testConfig.db.name}`;

db.ts や他のヘルパーはこの config.ts から import するだけで、process.env に直接触りません。
環境変数の読み取りと解釈が1箇所に集約される、値が as const で型付けされ、typoや型ミスマッチがコンパイル時に検出できる設定値の出どころを追うときに見る場所が1ファイルで済むというメリットがあります

Comment thread tests/helpers/db.ts Outdated
Comment thread .github/workflows/ci.yml
Comment thread docker-compose.test.yml Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本番環境がpostgres15なのでそっちを使えると良い

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ対応できてなさそう

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

posgtre15 に下げて検証回し、問題なく通ったのでコミット、プッシュしました!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mel-906 imageがpostgres:17-alphaになっているみたいだけど、私が何か間違えている?

Comment thread .github/workflows/ci.yml
Comment thread tests/helpers/db.ts Outdated
@Mel-906 Mel-906 changed the title DB結合テスト基盤の導入とrunInTransactionテストの移行 test : DB結合テスト基盤の導入とrunInTransactionテストの移行 Apr 1, 2026
@Mel-906
Copy link
Copy Markdown
Author

Mel-906 commented Apr 2, 2026

対応遅くなってしまいすみません。
ご指摘ありがとうございます。以下、まとめて対応しました。

  • _prisma_migrations については follow-up 用の Issue として #143 を起票し、tests/helpers/db.ts に「#143 解決後に暫定除外を削除する」TODO を追加しました。
  • drizzle-kit push の実行は、一貫性のため npx から vp run db:push -- --force に変更しました。
  • cleanDatabase()TRUNCATE では、将来的に予約語やハイフンを含むテーブル名でも壊れないよう、テーブル名を escapeIdentifier でクォートするようにしました。
  • integration test の CI は GitHub Actions の services.postgres ではなく、ローカルと同じ docker-compose.test.yml を使う形に寄せました。あわせて CI で .env.test を生成し、終了時に down するようにしています。

ローカルでも
docker compose --env-file .env.test -f docker-compose.test.yml up -d --wait の後に
npm run test:integration を実行し、問題なく通ることを確認しています。

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread tests/helpers/db.ts
Comment on lines +50 to 54
if (dbName !== testConfig.db.name) {
throw new Error(
`テスト用DBではないDBに接続しています: "${dbName}"。接続先が core_test であることを確認してください。`,
`テスト用DBではないDBに接続しています: "${dbName}"。接続先が ${testConfig.db.name} であることを確認してください。`,
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Safety guard now compares against configurable DB name instead of hardcoded value

The old code at tests/helpers/db.ts:50 hardcoded dbName !== "core_test" as a safety check to prevent running tests against a production database. The new code compares dbName !== testConfig.db.name, where testConfig.db.name comes from .env.test. This means the safety guard is now tautological — it will always pass as long as the connection succeeds, because the configured name and actual name will always match when the connection string is derived from the same config. If someone accidentally configures .env.test to point at a production database, this guard won't catch it. The old hardcoded check provided a stronger safety net. Whether this matters depends on the team's risk tolerance for test database misuse.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これは、確かに...どうしようか

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repo上に本番DBの情報直書きして防ぐみたいなのを思いましたがさすがにダメですよね...

DB接続先情報の一部のみハードに実装しますか?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localhostじゃなかったら警告するとかがいいかもしれない?

Comment thread tests/helpers/setupIntegration.ts
Comment thread .github/workflows/ci.yml
Comment thread docker-compose.test.yml Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ対応できてなさそう

Mel-906 and others added 3 commits April 2, 2026 22:16
Docker Compose + Vitest設定分離 + テストヘルパーによる結合テスト基盤を構築し、
既存のrunInTransactionテストを新基盤に移行した。
CIにintegrationテストジョブを追加。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Mel-906 Mel-906 force-pushed the feature/integration-test-infrastructure branch from dfb4694 to 7bd75f1 Compare April 2, 2026 13:19
@Mel-906 Mel-906 force-pushed the feature/uow-repository-transaction branch from b74198a to 44cd950 Compare April 2, 2026 13:19
devin-ai-integration[bot]

This comment was marked as resolved.

Comment thread docker-compose.test.yml Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mel-906 imageがpostgres:17-alphaになっているみたいだけど、私が何か間違えている?

Comment thread tests/helpers/db.ts
Comment on lines +50 to 54
if (dbName !== testConfig.db.name) {
throw new Error(
`テスト用DBではないDBに接続しています: "${dbName}"。接続先が core_test であることを確認してください。`,
`テスト用DBではないDBに接続しています: "${dbName}"。接続先が ${testConfig.db.name} であることを確認してください。`,
);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localhostじゃなかったら警告するとかがいいかもしれない?

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +1 to +7
import { beforeEach } from "vite-plus/test";
import "./config";
import { cleanDatabase } from "./db";

beforeEach(async () => {
await cleanDatabase();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Module import ordering ensures correct DATABASE_URL before pool creation

There's a subtle dependency: the production client.ts lazily reads process.env.DATABASE_URL only when getPool() is first called. The setupFiles config ensures setupIntegration.ts runs before test files, and it imports ./config which sets process.env.DATABASE_URL as a module-level side effect (tests/helpers/config.ts:43). Since the production pool is lazy (src/infrastructure/drizzle/client.ts:12-21), the env var is guaranteed to be set before the pool reads it. This works correctly but is fragile — if anyone imports the production client at module scope in a setupFile that runs before config, the pool would get an empty DATABASE_URL. A comment noting this ordering dependency would help future maintainers.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants