在構建應用時,您可能希望鎖定對 Cloud Firestore 數據庫的訪問。但是,在啟動之前,您需要更細緻的 Cloud Firestore 安全規則。使用 Cloud Firestore 模擬器,除了原型設計和測試應用程序的一般功能和行為之外,您還可以編寫單元測試來檢查 Cloud Firestore 安全規則的行為。
快速開始
對於一些具有簡單規則的基本測試用例,請試用快速入門示例。
了解 Cloud Firestore 安全規則
在使用移動和 Web 客戶端庫時,實施Firebase 身份驗證和Cloud Firestore 安全規則以進行無服務器身份驗證、授權和數據驗證。
Cloud Firestore 安全規則包括兩部分:
- 標識數據庫中文檔的
match
語句。 - 控制對這些文檔的訪問的
allow
表達式。
Firebase 身份驗證驗證用戶的憑據並為基於用戶和基於角色的訪問系統提供基礎。
在讀取或寫入任何數據之前,來自 Cloud Firestore 移動/Web 客戶端庫的每個數據庫請求都會根據您的安全規則進行評估。如果規則拒絕訪問任何指定的文檔路徑,則整個請求將失敗。
在開始使用 Cloud Firestore 安全規則 中了解有關 Cloud Firestore 安全規則的更多信息。
安裝模擬器
要安裝 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 安全規則的本地文件的名稱,並將這些規則應用於所有項目。如果您不提供本地文件路徑或使用如下所述的loadFirestoreRules
方法,模擬器會將所有項目視為具有開放規則。 - 雖然大多數 Firebase SDK直接與模擬器一起使用,但只有
@firebase/rules-unit-testing
庫支持安全規則中的模擬auth
,從而使單元測試更加容易。此外,該庫還支持一些特定於模擬器的功能,例如清除所有數據,如下所列。 - 模擬器還將接受通過客戶端 SDK 提供的生產 Firebase Auth 令牌並相應地評估規則,這允許您的應用程序在集成和手動測試中直接連接到模擬器。
運行本地單元測試
使用 v9 JavaScript SDK 運行本地單元測試
Firebase 分發了一個安全規則單元測試庫,其中包含第 9 版 JavaScript SDK 和第 8 版 SDK。庫 API 明顯不同。我們推薦 v9 測試庫,它更加精簡併且需要更少的設置來連接到模擬器,從而安全地避免意外使用生產資源。為了向後兼容,我們繼續提供 v8 測試庫。
使用@firebase/rules-unit-testing
模塊與本地運行的模擬器交互。如果出現超時或ECONNREFUSED
錯誤,請仔細檢查模擬器是否確實在運行。
我們強烈建議使用最新版本的 Node.js,以便您可以使用async/await
表示法。您可能想要測試的幾乎所有行為都涉及異步函數,並且測試模塊旨在與基於 Promise 的代碼一起使用。
v9 規則單元測試庫始終了解模擬器並且從不接觸您的生產資源。
您使用 v9 模塊化導入語句導入庫。例如:
import {
assertFails,
assertSucceeds,
initializeTestEnvironment,
RulesTestEnvironment,
} 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
實現模擬身份驗證狀態的測試用例。
常用方法和實用函數
另請參閱v9 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 Auth 令牌。
在測試中使用返回的測試上下文對象來訪問配置的任何模擬器實例,包括使用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'), { ... });
特定於模擬器的方法
另請參閱v9 SDK 中的常見測試方法和實用函數。
RulesTestEnvironment.clearFirestore() => Promise<void>
此方法清除 Firestore 數據庫中屬於為 Firestore 模擬器配置的projectId
數據。
RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;
此方法為此測試上下文獲取一個 Firestore 實例。返回的 Firebase JS 客戶端 SDK 實例可以與客戶端 SDK API(v9 模塊化或 v9 兼容)一起使用。
可視化規則評估
Cloud Firestore 模擬器可讓您在 Emulator Suite UI 中可視化客戶端請求,包括 Firebase 安全規則的評估跟踪。
打開Firestore > Requests選項卡以查看每個請求的詳細評估順序。
生成測試報告
運行一套測試後,您可以訪問測試覆蓋率報告,其中顯示瞭如何評估每個安全規則。
要獲取報告,請在模擬器運行時查詢公開的端點。對於瀏覽器友好的版本,請使用以下 URL:
http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html
這會將您的規則分解為表達式和子表達式,您可以將鼠標懸停在它們上以獲取更多信息,包括計算次數和返回值。對於此數據的原始 JSON 版本,請在查詢中包含以下 URL:
http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage
模擬器和生產之間的差異
- 您不必顯式創建 Cloud Firestore 項目。模擬器會自動創建任何被訪問的實例。
- Cloud Firestore 模擬器不適用於正常的 Firebase 身份驗證流程。相反,在 Firebase 測試 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 安全規則實際上不允許的方式修改數據。如果您的規則使測試設置變得複雜,請嘗試在設置步驟中使用RulesTestEnvironment.withSecurityRulesDisabled
,這樣讀取和寫入就不會觸發PERMISSION_DENIED
錯誤。
之後,您的測試可以分別使用RulesTestEnvironment.authenticatedContext
和unauthenticatedContext
作為經過身份驗證或未經身份驗證的用戶執行操作。這使您可以驗證您的 Cloud Firestore 安全規則是否正確允許/拒絕不同的情況。