在建構應用程式時,您可能會想鎖定 Cloud Firestore 資料庫的存取權。不過,在推出前,您需要更精細的 Cloud Firestore Security Rules。使用 Cloud Firestore 模擬器,除了製作原型並測試應用程式的一般功能和行為,您還可以編寫單元測試,檢查 Cloud Firestore Security Rules 的行為。
快速入門導覽課程
如需幾個含有簡單規則的基本測試案例,請試試快速入門範例。
瞭解 Cloud Firestore Security Rules
在使用行動和網路用戶端程式庫時,實作 Firebase Authentication 和 Cloud Firestore Security Rules 以進行無伺服器驗證、授權和資料驗證。
Cloud Firestore Security Rules 包含兩個部分:
match
陳述式,用於識別資料庫中的文件。- 用於控管這些文件存取權的
allow
運算式。
Firebase Authentication 會驗證使用者的憑證,並提供以使用者和角色為基礎的存取權系統基礎。
系統會先根據您的安全性規則評估 Cloud Firestore 行動/網路用戶端程式庫的每個資料庫要求,再讀取或寫入任何資料。如果規則拒絕存取任何指定文件路徑,整個要求就會失敗。
如要進一步瞭解 Cloud Firestore Security Rules,請參閱「開始使用 Cloud Firestore Security Rules」。
安裝模擬器
如要安裝 Cloud Firestore 模擬器,請使用 Firebase CLI 並執行下列指令:
firebase setup:emulators:firestore
執行模擬器
請先在工作目錄中初始化 Firebase 專案。這是使用 Firebase CLI 的常見第一步。
firebase init
使用下列指令啟動模擬器。模擬器會持續執行,直到您終止程序為止:
firebase emulators:start --only firestore
在許多情況下,您會想要啟動模擬器、執行測試套件,然後在測試執行後關閉模擬器。您可以使用 emulators:exec
指令輕鬆完成這項操作:
firebase emulators:exec --only firestore "./my-test-script.sh"
啟動模擬器時,模擬器會嘗試在預設通訊埠 (8080) 上執行。您可以修改 firebase.json
檔案的 "emulators"
區段來變更模擬器通訊埠:
{ // ... "emulators": { "firestore": { "port": "YOUR_PORT" } } }
執行模擬器之前
開始使用模擬器前,請注意下列事項:
- 模擬器一開始會載入
firebase.json
檔案firestore.rules
欄位中指定的規則。它會預期包含 Cloud Firestore Security Rules 的本機檔案名稱,並將這些規則套用至所有專案。如果您未提供本機檔案路徑,或使用下方所述的loadFirestoreRules
方法,模擬器會將所有專案視為具有開放規則。 - 雖然大多數 Firebase SDK 會直接與模擬器搭配運作,但只有
@firebase/rules-unit-testing
程式庫支援模擬安全性規則中的auth
,因此單元測試會變得更加簡單。此外,此程式庫支援一些模擬器專屬功能,例如清除所有資料,如下所列。 - 模擬器也會接受透過用戶端 SDK 提供的正式版 Firebase 驗證權杖,並據此評估規則,讓您可以在整合和手動測試中直接將應用程式連結至模擬器。
執行本機單元測試
使用 v9 JavaScript SDK 執行本機單元測試
Firebase 會透過其第 9 版 JavaScript SDK 和第 8 版 SDK 發布安全規則單元測試程式庫。程式庫 API 有顯著差異。我們建議您使用 v9 測試程式庫,因為它更精簡,且不需要太多設定就能連線至模擬器,因此可安全避免意外使用正式版資源。為了回溯相容性,我們會繼續提供 v8 測試程式庫。
使用 @firebase/rules-unit-testing
模組與在本機執行的模擬器互動。如果發生逾時或 ECONNREFUSED
錯誤,請再次確認模擬器是否正常運作。
強烈建議您使用最新版的 Node.js,以便使用 async/await
符號。您可能想要測試的幾乎所有行為都涉及非同步函式,而測試模組則是為了與以承諾為基礎的程式碼搭配使用而設計。
v9 規則單元測試程式庫一律會偵測模擬器,絕不會觸及您的正式版資源。
您可以使用 v9 模組匯入陳述式匯入程式庫。例如:
import {
assertFails,
assertSucceeds,
initializeTestEnvironment
} from "@firebase/rules-unit-testing"
// Use `const { … } = require("@firebase/rules-unit-testing")` if imports are not supported
// Or we suggest `const testing = require("@firebase/rules-unit-testing")` if necessary.
匯入後,實作單元測試涉及:
- 透過對
initializeTestEnvironment
的呼叫,建立及設定RulesTestEnvironment
。 - 使用可讓您暫時略過規則
RulesTestEnvironment.withSecurityRulesDisabled
的測試資料,在不觸發規則的情況下設定測試資料。 - 設定測試套件和個別測試前/後鉤子,並透過呼叫清理測試資料和環境,例如
RulesTestEnvironment.cleanup()
或RulesTestEnvironment.clearFirestore()
。 - 使用
RulesTestEnvironment.authenticatedContext
和RulesTestEnvironment.unauthenticatedContext
實作模擬驗證狀態的測試案例。
常用方法與公用程式函式
另請參閱 第 9 版 SDK 中的模擬器專屬測試方法。
initializeTestEnvironment() => RulesTestEnvironment
這個函式會初始化規則單元測試的測試環境。請先呼叫這個函式進行測試設定。您必須執行模擬器,才能順利執行。
這個函式會接受選用的物件,用於定義 TestEnvironmentConfig
,其中可包含專案 ID 和模擬器設定。
let testEnv = await initializeTestEnvironment({ projectId: "demo-project-1234", firestore: { rules: fs.readFileSync("firestore.rules", "utf8"), }, });
RulesTestEnvironment.authenticatedContext({ user_id: string, tokenOptions?: TokenOptions }) => RulesTestContext
這個方法會建立 RulesTestContext
,其行為類似於已驗證的驗證使用者。透過傳回的結構定義建立的要求會附上模擬驗證權符記。您可以選擇傳遞定義自訂宣稱或驗證權杖酬載值的覆寫值的物件。
在測試中使用傳回的測試內容物件,存取任何已設定的模擬器例項,包括使用 initializeTestEnvironment
設定的例項。
// Assuming a Firestore app and the Firestore emulator for this example import { setDoc } from "firebase/firestore"; const alice = testEnv.authenticatedContext("alice", { … }); // Use the Firestore instance associated with this context await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });
RulesTestEnvironment.unauthenticatedContext() => RulesTestContext
這個方法會建立 RulesTestContext
,其行為類似未透過驗證登入的用戶端。透過傳回結構定義建立的要求不會附加 Firebase 驗證權杖。
在測試中使用傳回的測試內容物件,存取已設定的所有模擬器執行個體,包括透過 initializeTestEnvironment
設定的模擬器執行個體。
// Assuming a Cloud Storage app and the Storage emulator for this example import { getStorage, ref, deleteObject } from "firebase/storage"; const alice = testEnv.unauthenticatedContext(); // Use the Cloud Storage instance associated with this context const desertRef = ref(alice.storage(), 'images/desert.jpg'); await assertSucceeds(deleteObject(desertRef));
RulesTestEnvironment.withSecurityRulesDisabled()
在與安全性規則停用時相同的情況下,執行測試設定函式。
這個方法採用回呼函式,該函式會接收安全性規則略過內容,並傳回承諾。承諾解析/拒絕後,系統就會銷毀這個內容。
RulesTestEnvironment.cleanup()
這個方法會刪除在測試環境中建立的所有 RulesTestContexts
,並清除基礎資源,以允許簡潔結束作業。
這個方法不會以任何方式變更模擬器的狀態。如要在測試之間重設資料,請使用應用程式模擬器專屬的清除資料方法。
assertSucceeds(pr: Promise<any>)) => Promise<any>
這是測試案例公用程式。
此函式宣告提供的 Promise 包裝模擬器作業,在沒有安全性規則違規事項的情況下完成解析。
await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });
assertFails(pr: Promise<any>)) => Promise<any>
這是測試案例公用程式。
這個函式會斷言,提供的包裝模擬器作業的 Promise 會因違反安全性規則而遭到拒絕。
await assertFails(setDoc(alice.firestore(), '/users/bob'), { ... });
模擬器專用方法
另請參閱 第 9 版 SDK 中的常見測試方法和公用函式。
RulesTestEnvironment.clearFirestore() => Promise<void>
這個方法會清除 Firestore 資料庫中屬於為 Firestore 模擬器設定的 projectId
的資料。
RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;
這個方法會取得此測試結構定義的 Firestore 執行個體。傳回的 Firebase JS Client SDK 例項可搭配用戶端 SDK API (第 9 版模組化或第 9 版相容性) 使用。
以圖表呈現規則評估結果
Cloud Firestore 模擬器可讓您在模擬器套件 UI 中以圖形化方式呈現用戶端要求,包括 Firebase 安全性規則的評估追蹤。
開啟「Firestore」>「要求」分頁標籤,即可查看每項要求的詳細評估順序。
產生測試報告
執行一系列測試後,您可以查看測試涵蓋率報表,瞭解每項安全性規則的評估方式。
如要取得報表,請在模擬器執行期間查詢公開的端點。如要使用瀏覽器友善版本,請使用下列網址:
http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html
這會將規則分解為運算式和子運算式,您可以將滑鼠游標懸停在運算式上,取得更多資訊,包括評估次數和傳回的值。針對這項資料的原始 JSON 版本,請在查詢中加入下列網址:
http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage
模擬器與正式環境的差異
- 您不必明確建立 Cloud Firestore 專案。模擬器會自動建立任何要存取的例項。
- Cloud Firestore 模擬器不適用於一般 Firebase Authentication 流程。相反地,在 Firebase Test SDK 中,我們已在
rules-unit-testing
程式庫中提供initializeTestApp()
方法,此方法會採用auth
欄位。使用這個方法建立的 Firebase 句柄,會以已成功驗證為您提供的實體的形式運作。如果您傳入null
,系統會將其視為未經驗證的使用者 (例如auth != null
規則會失敗)。
排解已知問題
使用 Cloud Firestore 模擬器時,您可能會遇到下列已知問題。請按照下列指示排解您遇到的任何異常行為。這些筆記是以安全規則單元測試程式庫為依據,但一般方法適用於任何 Firebase SDK。
測試行為不一致
如果測試偶爾會通過且失敗,即使測試本身未進行任何變更,您可能需要確認測試是否已正確排序。與模擬器的大部分互動都是非同步的,因此請仔細檢查所有非同步程式碼是否已正確排序。如要修正序列,您可以鏈結承諾項目或使用 await
標記法。
請特別查看下列非同步作業:
- 設定安全性規則,例如
initializeTestEnvironment
。 - 讀取及寫入資料,例如
db.collection("users").doc("alice").get()
。 - 作業斷言,包括
assertSucceeds
和assertFails
。
測試僅在您首次載入模擬器時通過
模擬器是有狀態的。它會將寫入的所有資料儲存在記憶體中,因此當模擬器關閉時,所有資料都會遺失。如果您針對相同專案 ID 執行多項測試,每項測試都可能產生可能影響後續測試的資料。您可以使用下列任一方法略過這項行為:
- 為每項測試使用專屬專案 ID。請注意,如果您選擇這麼做,就必須在每個測試中呼叫
initializeTestEnvironment
;系統只會為預設專案 ID 自動載入規則。 - 重新建構測試,以免與先前寫入的資料互動 (例如,為每項測試使用不同的集合)。
- 刪除測試期間寫入的所有資料。
測試設定非常複雜
設定測試時,您可能會希望以 Cloud Firestore Security Rules 實際禁止的方式修改資料。如果規則導致測試設定複雜,請嘗試在設定步驟中使用 RulesTestEnvironment.withSecurityRulesDisabled
,這樣讀取和寫入作業就不會觸發 PERMISSION_DENIED
錯誤。
之後,測試可以分別使用 RulesTestEnvironment.authenticatedContext
和 unauthenticatedContext
,以已驗證或未驗證的使用者身分執行作業。這樣一來,您就能驗證 Cloud Firestore Security Rules 是否正確允許/拒絕不同情況。