在建立應用程式時,您可能想要鎖定對 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
,其行為類似於經過驗證的 Authentication 使用者。透過傳回的上下文建立的請求將附加一個模擬身份驗證令牌。 (可選)傳遞定義自訂聲明或覆寫身份驗證令牌有效負載的物件。
在測試中使用傳回的測試上下文物件來存取配置的任何模擬器實例,包括使用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'), { ... });
仿真器特定的方法
另請參閱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 模擬器可讓您在模擬器套件 UI 中視覺化用戶端請求,包括 Firebase 安全規則的評估追蹤。
開啟Firestore > 請求標籤以查看每個請求的詳細評估順序。
產生測試報告
執行一套測試後,您可以存取測試覆蓋率報告,其中顯示每個安全規則的評估。
若要取得報告,請在模擬器執行時查詢模擬器上公開的端點。對於瀏覽器友善的版本,請使用以下 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 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 安全規則實際上不允許的方式修改資料。如果您的規則使測試設定變得複雜,請嘗試在設定步驟中使用RulesTestEnvironment.withSecurityRulesDisabled
,這樣讀取和寫入就不會觸發PERMISSION_DENIED
錯誤。
之後,您的測試可以分別使用RulesTestEnvironment.authenticatedContext
和unauthenticatedContext
以經過身份驗證或未經身份驗證的使用者身份執行操作。這允許您驗證您的 Cloud Firestore 安全性規則是否正確允許/拒絕不同的情況。