❯❯ 當 Mock 遇到了真 DB:7 處寫入讀取不對稱缺口,與一個 Commit 切換 Postgres 的真相
📍 流水線位置|入口 → flow → 型別 → 【mock】 → 測試 → UI → vibe → 上線(mock 與真實的接縫)

討論到第十二天,是時候來面對這套架構最常遭遇的質疑了(抖):
系統若長期建立於 Mock Server、Typed Client 與全數通過的 E2E 自動化測試上,是否只是在受控的隔離環境中進行驗證?當系統正式與實體後端及資料庫串接時,會不會暴露大量的架構落差與隱形缺口?
答案是:會😂
在此架構中,Mock Server 的資料狀態只留存於 Server 的記憶體陣列中,服務重新啟動就會重置為預設 Seed 資料。在串接實體資料庫之前,系統其實並未落實實體的資料持久化(Persistence)。
在準備切換到實體資料庫的預檢階段,我針對各模組進行了「寫入後到底能不能讀出來」的對帳檢查,沒想到卻發現了系統中存在 7 處寫入後無法經由讀取端點還原的結構性缺口。
追下去才發現,源頭竟出在需求規格(Specification)本身就定義偏了:
當初規格採行命令驅動(Command-driven)撰寫,明確規範了各項寫入動作(如「新增賓客」、「更新座位設定」),自動化生成腳本因而精確產生了寫入端點(HTTP POST / PUT)。
然而,部分業務狀態只定義了寫入行為,卻完全忘了規範對應的讀取檢索行為(HTTP GET)。這導致對應的讀取端點出現兩種情況:未被自動化腳本生成,或是生成的端點回應中遺漏了關鍵欄位。
偏偏在 UI 自動化生成階段,為了滿足前端元件的渲染與運作需求,生成腳本使用了前端區域狀態(Local State,如 ref / reactive)暫存寫入的資料。使用者進行儲存操作時,介面呈現成功更新的狀態,但該狀態僅存在於前端記憶體中,並未由 Server 端點返回。只要你按下 F5 重新整理,頁面重新向 Mock Server 抓資料,瞬間被打回原形、資料全數歸零。
那為什麼健檢當下我那 94 支自動化 E2E 測試完全沒抓到?
因為所有的測試案例都在單一 Page Session 裡一氣呵成:觸發寫入操作 → 斷言介面 DOM 改變 → 測試通過。沒有一支測試案例包含「重新載入頁面並驗證 Server 狀態」的步驟。
「寫入與讀取介面的不對稱」與「缺乏重新載入驗證的測試案例」兩者交疊,導致此類持久化缺口在 Mock 階段未能及時被自動化測試捕捉,我就這樣傻傻被騙過去了🥲。
抓到問題後,修復此類缺口的工程步驟包含:補齊缺漏的 GET 端點、補全回應欄位,並將前端介面由 Local State 暫存改為透過 useFetch 向 Server 端點請求狀態。
為防止這種「假持久化」的悲劇再次發生,我建立了專門驗證持久化行為的測試套件(persistence-*.spec.ts),強制所有的測試流程必須包含頁面重新載入(Reload)的驗證步驟。
以場地佈局模組之測試片段為例:
const apiCall = waitForApiCall(page, /\/venue-layout(\?|$)/, 'PUT')
await page.getByTestId('venue-submit').click()
// 等 PUT response 完成(DB 已 commit)再 reload,避免 GET 搶在寫入前讀到舊值的競態
const request = await apiCall
await request.response()
// 重整後重開 modal:值應由 GET 還原為剛存的,而非硬編預設
await page.reload({ waitUntil: 'networkidle' })
await page.getByTestId('venue-layout').click()
await expect(page.getByTestId('stage-width')).toHaveValue('999')

這套測試驗證流程是:發起寫入請求 → 確認 Server 端點回應完成 → 強制重新載入頁面 → 觸發讀取端點並斷言資料正確性。透過此流程,率先鋪設的 LINE 訊息、場地佈局與謝卡喜餅三個核心模組,再也無法用前端區域狀態掩蓋端點資料缺口。
這裡說明一下,雖然 7 處缺口的讀取端點已全數補齊,但這種重型持久化測試目前只覆蓋在這三個模組上,其餘模組尚未納入,這是刻意的取捨而非盲目追求 100% 覆蓋。
**※ 註:**程式碼中關於「避免 GET 搶在寫入前讀到舊值的競態」的註解,是後續正式串接 PostgreSQL 資料庫後,因為非同步寫入落盤延遲觸發測試失敗而補強的同步機制。
搞定 7 處介面缺口、補上持久化測試之後,就輪到實體資料庫的遷移工程:寫好 Drizzle ORM Schema、啟動本機 PostgreSQL 資料庫、導入初始化資料,並發起切換 commit(Commit 3fa5dff),將全專案 96 支 API Handler(僅 2 支特例除外)一次性由 Mock 記憶體操作切換為實體 PostgreSQL 資料庫查詢。
切換完成後,前端 UI 程式碼無需進行任何修改,全數 E2E 自動化測試當下維持綠燈通過。
這場原本以為會血流成河的串接,之所以能做到幾乎無痛,全靠先前立下的兩大工程護欄:
型別合約約束:
Mock 端點與實體資料庫查詢共用相同的 TypeScript Interface,只要欄位型別有半點不對,編譯階段就會直接擋下。
邏輯缺口前置修復:
介面不對稱與資料無法還原等問題,已經在對帳檢查與持久化測試階段完成修補。
Mock 階段暴露的問題,本質上並非 Mock Server 系統自身的缺陷,而是需求規格(Specification)不完整所導致的衍生風險:若規格僅定義了「寫入」而遺漏「讀取」,自動化生成工具與基礎 E2E 測試皆無法主動捕捉此類語意缺口。Mock Server 只不過是延後了這個規格漏洞曝光的時間點而已。
經過這次經驗,我升級了流水線規範:在從 Mock 遷移至實體資料庫前,必須強制對全系統欄位執行「寫入與讀取對應(CRUD Balance)」的對帳審查,確保規格定義的完整性。
這輪對帳也建立了一套標準處置節點:
狀態對帳 → 產出缺口報告 → 人工審查定案 → 補強修復 → 自動化驗證。

靠這套對帳與測試的雙重約束,終於有底氣把系統切換時的接縫風險,降到趨近於零。
然而,接上實體後端只是第一步。隨之而來的下一個硬考驗是:當後端的規格與資料庫 Schema 於後續迭代中持續發生變更時,流水線應如何維持前端與後端的狀態同步?
關於相關的同步機制與 Sync 模式,明天再來跟大家聊聊!
🎒 最小一步|找一個你專案裡有儲存功能的頁面,存完按 F5 重新整理,看資料是從 Server 端點完整讀回來的,還是只活在前端記憶體裡。
📎 本篇證據|Commit3de898a(包含 7 處介面缺口註記之 Persistence Specs)、Commit3fa5dff(94 支 Handler 切換至 PostgreSQL 之異動紀錄)、PR #8