測試您的 Cloud Firestore 安全規則

在建立應用程式時,您可能想要鎖定對 Cloud Firestore 資料庫的存取。但是,在啟動之前,您需要更細緻的 Cloud Firestore 安全性規則。使用 Cloud Firestore 模擬器,除了對應用程式的常規功能和行為進行原型設計和測試之外,您還可以編寫單元測試來檢查 Cloud Firestore 安全規則的行為。

快速開始

對於一些具有簡單規則的基本測試案例,請嘗試快速入門範例

了解 Cloud Firestore 安全性法則

使用行動和 Web 用戶端程式庫時,實作Firebase 驗證Cloud Firestore 安全性規則以進行無伺服器驗證、授權和資料驗證。

Cloud Firestore 安全規則包含兩個部分:

  1. 用於標識資料庫中的文件的match語句。
  2. 控制對這些文件的存取的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
} 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.authenticatedContextRulesTestEnvironment.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 > 請求標籤以查看每個請求的詳細評估順序。

Firestore 模擬器要求監視器顯示安全規則評估

產生測試報告

執行一套測試後,您可以存取測試覆蓋率報告,其中顯示每個安全規則的評估。

若要取得報告,請在模擬器執行時查詢模擬器上公開的端點。對於瀏覽器友善的版本,請使用以下 URL:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

這會將您的規則分解為表達式和子表達式,您可以將滑鼠懸停在這些表達式和子表達式上以獲取更多信息,包括計算數量和返回值。對於此資料的原始 JSON 版本,請在查詢中包含以下 URL:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

模擬器和生產之間的差異

  1. 您不必明確建立 Cloud Firestore 專案。模擬器會自動建立任何被存取的實例。
  2. 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()
  • 操作斷言,包括assertSucceedsassertFails

測試僅在第一次載入模擬器時通過

模擬器是有狀態的。它將寫入的所有資料儲存在記憶體中,因此只要模擬器關閉,所有資料都會遺失。如果您針對相同專案 ID 執行多個測試,則每個測試都可能產生可能影響後續測試的資料。您可以使用以下任一方法來繞過此行為:

  • 為每個測試使用唯一的項目 ID。請注意,如果您選擇這樣做,則需要在每個測試中呼叫initializeTestEnvironment ;僅針對預設項目 ID 自動載入規則。
  • 重構您的測試,使它們不與先前編寫的資料互動(例如,為每個測試使用不同的集合)。
  • 刪除測試期間寫入的所有資料。

測試設定非常複雜

設定測試時,您可能想要以 Cloud Firestore 安全規則實際上不允許的方式修改資料。如果您的規則使測試設定變得複雜,請嘗試在設定步驟中使用RulesTestEnvironment.withSecurityRulesDisabled ,這樣讀取和寫入就不會觸發PERMISSION_DENIED錯誤。

之後,您的測試可以分別使用RulesTestEnvironment.authenticatedContextunauthenticatedContext以經過身份驗證或未經身份驗證的使用者身份執行操作。這允許您驗證您的 Cloud Firestore 安全規則是否正確允許/拒絕不同的情況。