获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

測試您的 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 移動/網絡客戶端庫的每個數據庫請求。如果規則拒絕訪問任何指定的文檔路徑,則整個請求將失敗。

在 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 身份驗證令牌並相應地評估規則,從而允許在集成和手動測試中將您的應用程序直接連接到模擬器。

運行本地單元測試

使用 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.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 模擬器可讓您在 Emulator Suite UI 中可視化客戶端請求,包括 Firebase 安全規則的評估跟踪。

打開Firestore > Requests選項卡以查看每個請求的詳細評估順序。

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 測試 SDK 中,我們在rules-unit-testing庫中提供了initializeTestApp()方法,該方法採用auth字段。使用此方法創建的 Firebase 句柄的行為就像它已成功驗證為您提供的任何實體一樣。如果您傳入null ,它將表現為未經身份驗證的用戶(例如, auth != null規則將失敗)。

解決已知問題

在使用 Cloud Firestore 模擬器時,您可能會遇到以下已知問題。請按照以下指導解決您遇到的任何異常行為。這些說明是在考慮安全規則單元測試庫的情況下編寫的,但一般方法適用於任何 Firebase SDK。

測試行為不一致

如果您的測試偶爾通過和失敗,即使測試本身沒有任何更改,您可能需要驗證它們是否正確排序。與模擬器的大多數交互都是異步的,因此請仔細檢查所有異步代碼是否正確排序。您可以通過鏈接 promise 或自由使用await表示法來修復序列。

特別是,查看以下異步操作:

  • 設置安全規則,例如initializeTestEnvironment
  • 讀取和寫入數據,例如db.collection("users").doc("alice").get()
  • 操作斷言,包括assertSucceedsassertFails

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

模擬器是有狀態的。它將所有寫入它的數據存儲在內存中,因此只要模擬器關閉,任何數據都會丟失。如果您針對同一個項目 ID 運行多個測試,則每個測試都可以生成可能影響後續測試的數據。您可以使用以下任何一種方法來繞過此行為:

  • 為每個測試使用唯一的項目 ID。請注意,如果您選擇這樣做,則需要在每個測試中調用initializeTestEnvironment ;規則只會為默認項目 ID 自動加載。
  • 重構您的測試,使它們不會與以前編寫的數據交互(例如,為每個測試使用不同的集合)。
  • 刪除測試期間寫入的所有數據。

測試設置非常複雜

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

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