API テスト運用ガイド API Testing — Operations Guide
parky/api のテストは 単体 (unit) / E2E / 契約 (contract) の 3 層で構成します。
すべて Vitest ベース。単体は外部依存ゼロで数百ミリ秒、E2E は実 Supabase dev を叩き、
契約は OpenAPI スナップショットと生成型の drift を CI で検出します。
Tests for parky/api are layered across unit / E2E / contract.
Everything runs on Vitest. Unit tests have no external deps and finish in hundreds of milliseconds;
E2E hits the real dev Supabase; contract checks detect drift of the OpenAPI snapshot and generated types in CI.
3 層の役割分担Layering
-
単体 (Unit):
tests/unit/。lib/logger/lib/errors/lib/db.translatePgError等の純関数を BFF も Supabase も起動せずに検証。構造化ログのフィールド順、 LOG_LEVEL フィルタ、err.cause再帰、循環参照耐性まで含む。 -
Unit:
tests/unit/. Pure functions inlib/logger,lib/errors,lib/db.translatePgError, verified without any BFF or Supabase. Covers log field ordering, LOG_LEVEL filtering,err.causerecursion, and circular-reference safety. -
E2E:
tests/routes/。BFF (wrangler dev) + Supabase dev に対して HTTP 経由で叩き、レスポンスと副作用を検証。 -
E2E:
tests/routes/. Hits the BFF (wrangler dev) + Supabase dev over HTTP and checks responses and side effects. -
契約 (Contract):
docs/openapi.jsonと@parky/api-types/src/openapi.d.tsの drift を.github/workflows/api-ci.ymlのcontract-drift-checkjob が PR 時に検出する。テストと独立。詳細は本ページ末尾の「関連リンク」参照。 -
Contract: the
contract-drift-checkjob in.github/workflows/api-ci.ymlwatches drift betweendocs/openapi.jsonand@parky/api-types/src/openapi.d.tson every PR. Independent from unit / E2E. See the "Related" section below.
TL;DRTL;DR
# 1. 初回だけ(E2E で必要。単体だけなら不要)
cp parky/api/tests/.env.test.example parky/api/tests/.env.test
# → SUPABASE_URL / _SERVICE_ROLE_KEY / _ANON_KEY を埋める
# (値は parky/api/scripts/secrets.env と同じで OK)
# 2. 別ターミナルで BFF を起動(E2E で必要。単体だけなら不要)
cd parky/api && npm run dev # wrangler dev が :8787
# 3. テストを走らせる
cd parky/api
npm run test:unit # 単体のみ (外部依存なし、ms オーダー)
npm run test:unit:watch # 単体を watch モード
npm run test:e2e # E2E のみ (BFF + Supabase 必要)
npm run test # 単体 + E2E まとめて (= npm run test:all)
npm run test:changed # git 差分から影響するテストだけ自動選別して実行
npm run test:cleanup # テスト痕跡を全消去(残骸があった時の手動リセット)
設計方針Design principles
なぜ実 Supabase を叩くのかWhy hit the real Supabase
Supabase の JWT は JWKS(非対称鍵)検証なのでローカルの HS256 署名では通りません。
そのためテストユーザーを実際に auth.users に作り、password grant で JWT を取得します。
Hyperdrive + postgres.js 経由の挙動、PL/pgSQL RPC、RLS バイパスなど、BFF の
本番と同じコード経路を踏めるので、モックより信頼度が高くなります。
Supabase JWTs are verified via JWKS (asymmetric keys), so local HS256 signatures do not pass.
Tests create real users in auth.users and obtain JWTs via the password grant.
This exercises the BFF's real code path — Hyperdrive + postgres.js, PL/pgSQL RPCs,
RLS bypass — which is far more trustworthy than mocks.
なぜ wrangler dev ローカルかWhy local wrangler dev
- Cloudflare Hyperdrive / Queues / KV などの binding がそのまま使える。
- All the Cloudflare bindings (Hyperdrive / Queues / KV) work as-is.
- 複数開発者が dev-api を共有して干渉することがない。
- Multiple developers don't contend over a shared dev-api.
- CI でも同じ手順で走らせられる(
wrangler dev --env dev &→ test)。 - CI runs the exact same recipe (
wrangler dev --env dev &→ test).
データ汚染防止(冪等性ルール)Data-pollution prevention (idempotency rules)
テストが作るレコードはすべて本番と識別可能なプレフィックスを持たせます。
Every record the tests create carries a prefix that is distinguishable from production.
| 対象Target | ルールRule |
|---|---|
auth.users.email |
__test__e2e_<scope>+<uid>@parky-e2e.test
(.test TLD と __test__ プレフィックスの二重ガード)
(double-guarded by the .test TLD and a __test__ prefix)
|
app_users.email / display_name |
__TEST__ プレフィックス + scope 名prefix + scope name |
parking_lots.name |
__TEST__lot_<uuid8> |
tags.name |
__TEST__tag_<uuid6> |
user_search_presets.name |
__TEST__preset_<...> |
掃除は三段構え。すべて冪等で、何度実行しても結果は同じになります。
Cleanup runs at three layers. Every layer is idempotent — repeating a run has no extra side-effects.
- 各
afterAll()でcleanupAll()を呼び、そのテストが作成した ID を実削除。 - Each
afterAll()callscleanupAll()to delete the IDs the test created. - Vitest の
globalSetup終了時にcleanupByPrefix()でプレフィックス一致を一掃(落ちたテストの救済)。 - Vitest's
globalSetuptear-down runscleanupByPrefix()to sweep anything matching the prefix (safety net for tests that crashed). npm run test:cleanupで手動リセット(CI / 緊急時)。npm run test:cleanupruns the cleanup on-demand (CI or manual reset).
POST 系は本番衝突しないデータで行うのが鉄則。
例として email は .test TLD、name は __TEST__ プレフィックス必須です。
新しい fixture を追加する時もこのルールを守ってください。
POST tests must use data that cannot collide with production.
Emails use the .test TLD; names carry the __TEST__ prefix.
Keep this invariant when adding new fixtures.
新しいテストを追加する手順How to add a new test
-
tests/routes/<name>.test.tsを作る。ファイル名規約でsrc/routes/<name>.tsが自動的にカバー対象になります。 -
Create
tests/routes/<name>.test.ts. The naming convention automatically maps it tosrc/routes/<name>.ts. -
例外的に別ファイルも覆いたい場合は冒頭に
// @covers src/routes/xxx.ts, src/lib/yyy.tsを書く。 -
To cover extra files, add
// @covers src/routes/xxx.ts, src/lib/yyy.tsat the top. -
admin は
tests/routes/admin-<name>.test.ts→src/routes/admin/<name>.ts、 owner はtests/routes/owner-<name>.test.ts→src/routes/owner/<name>.ts。 -
Admin routes use
tests/routes/admin-<name>.test.ts→src/routes/admin/<name>.ts, and owner routes usetests/routes/owner-<name>.test.ts→src/routes/owner/<name>.ts. -
fixture は
createTestUser()/createTestParkingLot()/promoteToAdmin()から組み立てる。 -
Build fixtures via
createTestUser(),createTestParkingLot(),promoteToAdmin(). -
最後に必ず
afterAll(() => cleanupAll())を置く。 -
Always end with
afterAll(() => cleanupAll()). -
npm run test:changedで確認する(test:mapは自動で再生成されます)。 -
Verify with
npm run test:changed(the route map regenerates automatically).
Impact 解析(test:changed の仕組み)Impact analysis (how test:changed works)
何が起きるかWhat happens
-
tests/impact/build-route-map.tsを実行してroute-map.jsonを最新化。 -
Run
tests/impact/build-route-map.tsto refreshroute-map.json. -
git diff --name-onlyで変更ファイルを取得(優先順:origin/main...HEAD→main...HEAD→HEAD)。 -
Collect changed files with
git diff --name-only(priority:origin/main...HEAD→main...HEAD→HEAD). - 変更ファイル → route-map から「走らせるテスト集合」を展開。
- Expand changed files → the set of tests to run via the route map.
-
以下の場合は 全テスト走行(force-all):
tests/setup/**/tests/impact/**/tests/vitest.config.tspackage.json/package-lock.json/tsconfig.json/wrangler.toml- 変更が
api/配下にあるが route-map に載っていない場合(新規ファイル等)
-
Force-all triggers (run every test):
tests/setup/**/tests/impact/**/tests/vitest.config.tspackage.json/package-lock.json/tsconfig.json/wrangler.toml- Changes inside
api/that the route map doesn't yet know about (e.g. brand-new files).
route-map の中身What the route map contains
tests/impact/route-map.json は「src/* ファイル → 走らせる test ファイル群」の逆引き辞書です。
tests/impact/route-map.json is a reverse index from each src/* file to the tests that should run.
{
"src/routes/me-search-presets.ts": ["tests/routes/me-search-presets.test.ts"],
"src/lib/db.ts": ["tests/routes/admin-tags.test.ts", "tests/routes/me-search-presets.test.ts", ...],
"__always__": ["tests/routes/..."]
}
粒度は 2 種類:
The map uses two granularities:
-
route 単位:
src/routes/xxx.tsの変更 → そのルートのテストだけ。 -
Route-level: a change to
src/routes/xxx.tsruns only that route's test. -
共通モジュール:
src/lib/db.ts/src/middleware/auth.tsなど、 COMMON_COVERAGE に登録された "tripwire" は全テスト対象。 -
Shared modules: anything listed in COMMON_COVERAGE
(
src/lib/db.ts,src/middleware/auth.ts, etc.) triggers every test.
route-map.json はビルド成果物なので .gitignore 済み。
test:map コマンドまたは test:changed / test:all 実行時に毎回再生成されます。手で編集しないでください。
route-map.json is a build artifact (gitignored). It is regenerated on every
test:map, test:changed, or test:all run. Never edit it by hand.
@covers ディレクティブThe @covers directive
テストファイル冒頭のコメントで上書き・追加できます。カンマ区切りまたは複数行で列挙可。 ディレクティブがあればファイル名規約は上書きされます。
Declare extra or overriding coverage in a top-of-file comment. Comma-separated or multi-line. When present, the directive overrides the filename convention.
// tests/routes/me-vehicles.test.ts
// @covers src/routes/vehicles.ts
import { describe, expect, it } from "vitest";
// …
よくあるトラブルTroubleshooting
BFF が /healthz に応答しませんでした
別ターミナルで npm run dev を起動し忘れています。wrangler dev が
起動完了メッセージを出してから npm run test を打ちます。
npm run dev isn't running in another terminal. Wait until
wrangler dev prints its ready banner, then start the test run.
auth.admin.createUser failed
SUPABASE_SERVICE_ROLE_KEY が間違っているか、本番キーを入れてしまっています。
tests/.env.test を確認してください。
Wrong SUPABASE_SERVICE_ROLE_KEY — often a production key instead of dev.
Recheck tests/.env.test.
前回のテスト痕跡が残ったStale data from a previous run
npm run test:cleanup を 1 回だけ叩けばプレフィックス一致のデータをすべて消去します。
Run npm run test:cleanup once — it removes everything matching the test prefix.
Hyperdrive の接続エラー
Hyperdrive connection error
wrangler dev は Hyperdrive binding を remote に繋ぎに行きます。
オフライン時はテストを回せません(現時点ではオフライン対応スコープ外)。
wrangler dev dials into the remote Hyperdrive binding.
Tests can't run while offline (offline mode is out of scope).
ディレクトリ構成Layout
単体テストの追加: tests/unit/<name>.test.ts を作り、
ファイル冒頭に // @covers src/lib/xxx.ts を書く(ファイル名規約のフォールバックは無い)。
カバー対象が複数なら @covers をカンマ区切りで並べる。
Adding unit tests: create tests/unit/<name>.test.ts and
declare // @covers src/lib/xxx.ts at the top (no filename-convention fallback).
Multiple targets can be listed comma-separated.
関連リンクRelated
- Swagger UI / Redoc — テスト対象の OpenAPI 仕様the OpenAPI spec under test
- エラーカタログError catalog — テストが期待する
error.codeの一覧theerror.codevalues tests assert on - Cloudflare R2/Hyperdrive/Queues セットアップ手順setup
- 共通規約Conventions