測試安全性規則

建構應用程式時,您可能想鎖定對 Firestore 原生模式資料庫的存取權。不過,在正式推出前,您需要更精細的 Firestore 安全性規則。除了原型設計及測試應用程式的一般功能和行為,您還可以使用 Firestore 原生模式模擬器編寫單元測試,檢查 Firestore 安全性規則的行為。

快速入門導覽課程

如要使用簡單規則進行幾項基本測試,請試用快速入門範例

瞭解 Firestore 安全性規則

使用行動和網路用戶端程式庫時,請導入 Firebase 驗證Firestore 安全性規則,以便進行無伺服器服務驗證、授權和資料驗證。

Firestore 安全性規則包含兩部分:

  1. 用於識別資料庫中文件的 match 陳述式。
  2. 用來控管這些文件存取權的 allow 運算式。

Firebase 驗證會驗證使用者憑證,並為以使用者和角色為基礎的存取系統奠定基礎。

Firestore 行動/網路用戶端程式庫的每項資料庫要求,都會先經過安全性規則評估,才會讀取或寫入任何資料。如果規則拒絕存取任何指定的文件路徑,整個要求就會失敗。

如要進一步瞭解 Firestore 安全性規則,請參閱「開始使用 Firestore 安全性規則」。

安裝模擬器

如要安裝原生模式的 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 欄位中指定的規則。這個指令會預期 Firestore 安全性規則的本機檔案名稱,並將這些規則套用至所有專案。如果您未提供本機檔案路徑,或使用下文所述的 loadFirestoreRules 方法,模擬器會將所有專案視為具有開放規則。
  • 雖然大多數 Firebase SDK 都能直接搭配模擬器使用,但只有 @firebase/rules-unit-testing 程式庫支援模擬安全性規則中的 auth,因此可大幅簡化單元測試。此外,程式庫還支援幾項模擬器專屬功能,例如清除所有資料,如下所示。
  • 模擬器也會接受透過 Client SDK 提供的正式版 Firebase Auth 權杖,並據此評估規則,因此您可以在整合和手動測試中,將應用程式直接連線至模擬器。

執行本機單元測試

使用 JavaScript SDK 第 9 版執行本機單元測試

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().doc('/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()

使用行為如同安全性規則已停用的內容,執行測試設定函式。

這個方法會採用回呼函式,該函式會採用 Security-Rules-bypassing 環境,並傳回 Promise。承諾解決 / 拒絕後,內容就會遭到破壞。

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 資料庫中屬於 projectId 的資料,這些資料已設定為 Firestore 模擬器。

RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;

這個方法會取得此測試環境的 Firestore 執行個體。傳回的 Firebase JS Client SDK 執行個體可用於 Client SDK API (v9 模組化或 v9 相容)。

以視覺化方式呈現規則評估結果

透過 Native Mode 的 Firestore 模擬器,您可以在模擬器套件 UI 中查看用戶端要求,包括 Firebase 安全性規則的評估追蹤記錄。

開啟「Firestore」>「要求」分頁,查看每項要求的詳細評估順序。

Firestore 模擬器要求監控器,顯示安全性規則評估結果

產生測試報告

執行一系列測試後,您就能存取測試涵蓋範圍報表,瞭解每項安全性規則的評估方式。

如要取得報表,請在模擬器執行時查詢公開端點。如要使用瀏覽器友善版本,請前往下列網址:

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

這會將規則拆解為運算式和子運算式,您可以將滑鼠游標懸停在這些項目上,查看更多資訊,包括評估次數和傳回的值。如要取得這項資料的原始 JSON 版本,請在查詢中加入下列網址:

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

模擬器與正式環境的差異

  1. 您不必明確建立 Native Mode 的 Firestore 專案。模擬器會自動建立所存取的任何執行個體。
  2. 原生模式的 Firestore 不支援一般的 Firebase 驗證流程。我們在 Firebase 測試 SDK 的 rules-unit-testing 程式庫中提供 initializeTestApp() 方法,可接受 auth 欄位。使用這個方法建立的 Firebase 控制代碼,會如同您提供的任何實體已成功通過驗證。如果您傳遞 null,系統會將其視為未經驗證的使用者 (例如,auth != null 規則會失敗)。

排解已知問題

使用 Firestore (原生模式) 模擬器時,您可能會遇到下列已知問題。請按照下方指引排解任何異常行為。這些注意事項是針對安全防護規則單元測試程式庫編寫,但一般方法適用於任何 Firebase SDK。

測試行為不一致

如果測試偶爾會通過,偶爾會失敗,即使測試本身沒有任何變更,您可能也需要確認測試是否已正確排序。與模擬器的互動大多為非同步,因此請仔細檢查所有非同步程式碼是否依序執行。如要修正順序,可以串連 Promise,或大量使用 await 標記。

請特別查看下列非同步作業:

  • 設定安全性規則,例如 initializeTestEnvironment
  • 讀取及寫入資料,例如 db.collection("users").doc("alice").get()
  • 作業判斷,包括 assertSucceedsassertFails

測試只會在首次載入模擬器時通過

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

  • 請為每項測試使用專屬專案 ID。請注意,如果您選擇這麼做,就必須在每個測試中呼叫 initializeTestEnvironment;系統只會自動載入預設專案 ID 的規則。
  • 重新架構測試,確保測試不會與先前寫入的資料互動 (例如,為每個測試使用不同的集合)。
  • 刪除測試期間寫入的所有資料。

測試設定非常複雜

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

之後,您的測試就能分別使用 RulesTestEnvironment.authenticatedContextunauthenticatedContext,以已驗證或未驗證使用者的身分執行作業。這樣一來,您就能驗證 Firestore 安全性規則是否正確允許 / 拒絕不同情況。