構建單元測試

Firebase 本地模擬器套件可以更輕鬆地全面驗證您的應用的功能和行為。它也是驗證 Firebase 安全規則配置的絕佳工具。使用 Firebase 模擬器在本地環境中運行和自動化單元測試。本文檔中概述的方法應該可以幫助您為應用程序構建和自動化單元測試來驗證您的規則。

如果您還沒有設置 Firebase 模擬器,請設置它

運行模擬器之前

在開始使用模擬器之前,請記住以下幾點:

  • 模擬器最初將加載firebase.json文件的firestore.rules或“storage.rules”字段中指定的規則。如果該文件不存在並且您不使用如下所述的loadFirestoreRules或“loadStorageRules”方法,則模擬器會將所有項目視為具有開放規則。
  • 雖然大多數 Firebase SDK直接與模擬器配合使用,但只有@firebase/rules-unit-testing庫支持安全規則中的模擬auth ,從而使單元測試變得更加容易。此外,該庫還支持一些特定於模擬器的功能,例如清除所有數據,如下所列。
  • 模擬器還將接受通過客戶端 SDK 提供的生產 Firebase Auth 令牌並相應地評估規則,這允許在集成和手動測試中將您的應用程序直接連接到模擬器。

數據庫模擬器和生產環境之間的差異

  • 您不必顯式創建數據庫實例。模擬器將自動創建任何被訪問的數據庫實例。
  • 每個新數據庫都以封閉規則啟動,因此非管理員用戶將無法讀取或寫入。
  • 每個模擬數據庫都應用Spark 計劃限制和配額(最值得注意的是,這將每個實例限制為 100 個並發連接)。
  • 任何數據庫都會接受字符串"owner"作為管理員身份驗證令牌。
  • 模擬器目前無法與其他 Firebase 產品進行交互。值得注意的是,正常的 Firebase 身份驗證流程不起作用。相反,您可以使用rules-unit-testing庫中的initializeTestApp()方法,該方法採用auth字段。使用此方法創建的 Firebase 對象的行為就好像它已成功通過您提供的任何實體的身份驗證。如果您傳入null ,它將表現為未經身份驗證的用戶(例如auth != null規則將失敗)。

與實時數據庫模擬器交互

生產 Firebase 實時數據庫實例可以在firebaseio.com的子域中訪問,並且您可以像這樣訪問 REST api:

https://<database_name>.firebaseio.com/path/to/my/data.json

模擬器在本地運行,可通過localhost:9000訪問。要與特定數據庫實例交互,您必須使用ns查詢參數來指定數據庫名稱。

http://localhost:9000/path/to/my/data.json?ns=<database_name>

使用版本 9 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實現模擬身份驗證狀態的測試用例。

常用方法和實用函數

另請參閱使用模塊化 API 的仿真器特定測試方法

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'), { ... });

仿真器特定的方法

另請參閱使用模塊化 API 的常見測試方法和實用函數

雲Firestore

雲Firestore

RulesTestEnvironment.clearFirestore() => Promise<void>

此方法會清除 Firestore 數據庫中屬於為 Firestore 模擬器配置的projectId數據。

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

此方法獲取此測試上下文的 Firestore 實例。返回的 Firebase JS 客戶端 SDK 實例可與客戶端 SDK API(v9 模塊化或 v9 兼容)一起使用。

實時數據庫

實時數據庫

RulesTestEnvironment.clearDatabase() => Promise<void>

此方法清除實時數據庫中屬於為實時數據庫模擬器配置的projectId數據。

RulesTestContext.database(databaseURL?: Firestore.FirestoreSettings) => Firestore;

獲取此測試上下文的實時數據庫實例。返回的 Firebase JS 客戶端 SDK 實例可與客戶端 SDK API(模塊化或命名空間、版本 9 或更高版本)一起使用。該方法接受實時數據庫實例的 URL。如果指定,則返回命名空間的模擬版本的實例,其中包含從 URL 中提取的參數。

雲儲存

雲儲存

RulesTestEnvironment.clearStorage() => Promise<void>

此方法會清除屬於為 Cloud Storage 模擬器配置的projectId存儲桶中的對象和元數據。

RulesTestContext.storage(bucketUrl?: string) => Firebase Storage;

此方法返回一個配置為連接到模擬器的存儲實例。該方法接受 Firebase 存儲桶的gs:// url 進行測試。如果指定,則返回存儲桶名稱的模擬版本的存儲實例。

使用 v8 JavaScript SDK 運行本地單元測試

選擇一個產品以查看 Firebase Test SDK 用於與模擬器交互的方法。

雲Firestore

initializeTestApp({ projectId: string, auth: Object }) => FirebaseApp

此方法返回與選項中指定的項目 ID 和 auth 變量相對應的已初始化 Firebase 應用。使用它來創建一個作為特定用戶進行身份驗證的應用程序以在測試中使用。

firebase.initializeTestApp({
  projectId: "my-test-project",
  auth: { uid: "alice", email: "alice@example.com" }
});

initializeAdminApp({ projectId: string }) => FirebaseApp

此方法返回一個已初始化的管理 Firebase 應用程序。該應用程序在執行讀取和寫入時繞過安全規則。使用它來創建一個經過管理員身份驗證的應用程序來設置測試狀態。

firebase.initializeAdminApp({ projectId: "my-test-project" });
    

apps() => [FirebaseApp]此方法返回所有當前初始化的測試和管理應用程序。使用它來在測試之間或之後清理應用程序。

Promise.all(firebase.apps().map(app => app.delete()))

loadFirestoreRules({ projectId: string, rules: Object }) => Promise

此方法將規則發送到本地運行的數據庫。它採用一個將規則指定為字符串的對象。使用此方法來設置數據庫的規則。

firebase.loadFirestoreRules({
  projectId: "my-test-project",
  rules: fs.readFileSync("/path/to/firestore.rules", "utf8")
});
    

assertFails(pr: Promise) => Promise

此方法返回一個承諾,如果輸入成功則拒絕該承諾,或者如果輸入被拒絕則返回成功。使用此斷言數據庫讀取或寫入是否失敗。

firebase.assertFails(app.firestore().collection("private").doc("super-secret-document").get());
    

assertSucceeds(pr: Promise) => Promise

如果輸入成功,此方法將返回一個 Promise,如果輸入被拒絕,則該 Promise 會被拒絕。使用此斷言數據庫讀取或寫入是否成功。

firebase.assertSucceeds(app.firestore().collection("public").doc("test-document").get());
    

clearFirestoreData({ projectId: string }) => Promise

此方法會清除與本地運行的 Firestore 實例中的特定項目關聯的所有數據。測試後用此方法進行清理。

firebase.clearFirestoreData({
  projectId: "my-test-project"
});
   

實時數據庫

實時數據庫

initializeTestApp({ databaseName: string, auth: Object }) => FirebaseApp

使用它來創建一個作為特定用戶進行身份驗證的應用程序以在測試中使用。

返回與選項中指定的數據庫名稱和身份驗證變量覆蓋相對應的已初始化 Firebase 應用程序。

firebase.initializeTestApp({
  databaseName: "my-database",
  auth: { uid: "alice" }
});

initializeAdminApp({ databaseName: string }) => FirebaseApp

使用它來創建一個經過管理員身份驗證的應用程序來設置測試狀態。

返回與選項中指定的數據庫名稱相對應的已初始化管理 Firebase 應用程序。該應用程序在讀取和寫入數據庫時繞過安全規則。

firebase.initializeAdminApp({ databaseName: "my-database" });

loadDatabaseRules({ databaseName: string, rules: Object }) => Promise

使用它來設置數據庫的規則。

將規則發送到本地運行的數據庫。接受一個選項對象,將您的“databaseName”和“rules”指定為字符串。

firebase
      .loadDatabaseRules({
        databaseName: "my-database",
        rules: "{'rules': {'.read': false, '.write': false}}"
      });

apps() => [FirebaseApp]

返回所有當前初始化的測試和管理應用程序。

使用它來在測試之間或之後清理應用程序(請注意,具有活動偵聽器的初始化應用程序會阻止 JavaScript 退出):

 Promise.all(firebase.apps().map(app => app.delete()))

assertFails(pr: Promise) => Promise

如果輸入成功則返回一個 Promise,如果輸入被拒絕則返回成功。

使用它來斷言數據庫讀取或寫入失敗:

firebase.assertFails(app.database().ref("secret").once("value"));

assertSucceeds(pr: Promise) => Promise

如果輸入成功則返回一個 Promise,如果輸入被拒絕則返回一個 Promise。

使用它來斷言數據庫讀取或寫入成功:

firebase.assertSucceeds(app.database().ref("public").once("value"));

雲儲存

雲儲存

initializeTestApp({ storageBucket: string, auth: Object }) => FirebaseApp

使用它來創建一個作為特定用戶進行身份驗證的應用程序以在測試中使用。

返回與選項中指定的存儲桶名稱和身份驗證變量覆蓋相對應的已初始化 Firebase 應用程序。

firebase.initializeTestApp({
  storageBucket: "my-bucket",
  auth: { uid: "alice" }
});

initializeAdminApp({ storageBucket: string }) => FirebaseApp

使用它來創建一個經過管理員身份驗證的應用程序來設置測試狀態。

返回與選項中指定的存儲桶名稱相對應的已初始化管理 Firebase 應用程序。此應用程序在讀取和寫入存儲桶時繞過安全規則。

firebase.initializeAdminApp({ storageBucket: "my-bucket" });

loadStorageRules({ storageBucket: string, rules: Object }) => Promise

使用它來設置存儲桶的規則。

將規則發送到本地管理的存儲桶。接受一個選項對象,將“storageBucket”和“rules”指定為字符串。

firebase
      .loadStorageRules({
        storageBucket: "my-bucket",
        rules: fs.readFileSync("/path/to/storage.rules", "utf8")
      });

apps() => [FirebaseApp]

返回所有當前初始化的測試和管理應用程序。

使用它來在測試之間或之後清理應用程序(請注意,具有活動偵聽器的初始化應用程序會阻止 JavaScript 退出):

 Promise.all(firebase.apps().map(app => app.delete()))

assertFails(pr: Promise) => Promise

如果輸入成功則返回一個 Promise,如果輸入被拒絕則返回成功。

使用此斷言存儲桶讀取或寫入失敗:

firebase.assertFails(app.storage().ref("letters/private.doc").getMetadata());

assertSucceeds(pr: Promise) => Promise

如果輸入成功則返回一個 Promise,如果輸入被拒絕則返回一個 Promise。

使用它來斷言存儲桶讀取或寫入成功:

firebase.assertFails(app.storage().ref("images/cat.png").getMetadata());

JS SDK v8 的 RUT 庫 API

選擇一個產品以查看 Firebase Test SDK 用於與模擬器交互的方法。

雲Firestore

雲Firestore

initializeTestApp({ projectId: string, auth: Object }) => FirebaseApp

此方法返回與選項中指定的項目 ID 和 auth 變量相對應的已初始化 Firebase 應用。使用它來創建一個作為特定用戶進行身份驗證的應用程序以在測試中使用。

firebase.initializeTestApp({
  projectId: "my-test-project",
  auth: { uid: "alice", email: "alice@example.com" }
});

initializeAdminApp({ projectId: string }) => FirebaseApp

此方法返回一個已初始化的管理 Firebase 應用程序。該應用程序在執行讀取和寫入時繞過安全規則。使用它來創建一個經過管理員身份驗證的應用程序來設置測試狀態。

firebase.initializeAdminApp({ projectId: "my-test-project" });
    

apps() => [FirebaseApp]此方法返回所有當前初始化的測試和管理應用程序。使用它來在測試之間或之後清理應用程序。

Promise.all(firebase.apps().map(app => app.delete()))

loadFirestoreRules({ projectId: string, rules: Object }) => Promise

此方法將規則發送到本地運行的數據庫。它採用一個將規則指定為字符串的對象。使用此方法來設置數據庫的規則。

firebase.loadFirestoreRules({
  projectId: "my-test-project",
  rules: fs.readFileSync("/path/to/firestore.rules", "utf8")
});
    

assertFails(pr: Promise) => Promise

此方法返回一個承諾,如果輸入成功,則該承諾被拒絕;如果輸入被拒絕,則該承諾成功。使用此斷言數據庫讀取或寫入是否失敗。

firebase.assertFails(app.firestore().collection("private").doc("super-secret-document").get());
    

assertSucceeds(pr: Promise) => Promise

如果輸入成功,此方法將返回一個 Promise,如果輸入被拒絕,則該 Promise 會被拒絕。使用此斷言數據庫讀取或寫入是否成功。

firebase.assertSucceeds(app.firestore().collection("public").doc("test-document").get());
    

clearFirestoreData({ projectId: string }) => Promise

此方法會清除與本地運行的 Firestore 實例中的特定項目關聯的所有數據。測試後用此方法進行清理。

firebase.clearFirestoreData({
  projectId: "my-test-project"
});
   

實時數據庫

實時數據庫

initializeTestApp({ databaseName: string, auth: Object }) => FirebaseApp

使用它來創建一個作為特定用戶進行身份驗證的應用程序以在測試中使用。

返回與選項中指定的數據庫名稱和身份驗證變量覆蓋相對應的已初始化 Firebase 應用程序。

firebase.initializeTestApp({
  databaseName: "my-database",
  auth: { uid: "alice" }
});

initializeAdminApp({ databaseName: string }) => FirebaseApp

使用它來創建一個經過管理員身份驗證的應用程序來設置測試狀態。

返回與選項中指定的數據庫名稱相對應的已初始化管理 Firebase 應用程序。該應用程序在讀取和寫入數據庫時繞過安全規則。

firebase.initializeAdminApp({ databaseName: "my-database" });

loadDatabaseRules({ databaseName: string, rules: Object }) => Promise

使用它來設置數據庫的規則。

將規則發送到本地運行的數據庫。接受一個選項對象,將您的“databaseName”和“rules”指定為字符串。

firebase
      .loadDatabaseRules({
        databaseName: "my-database",
        rules: "{'rules': {'.read': false, '.write': false}}"
      });

apps() => [FirebaseApp]

返回所有當前初始化的測試和管理應用程序。

使用它來在測試之間或之後清理應用程序(請注意,具有活動偵聽器的初始化應用程序會阻止 JavaScript 退出):

 Promise.all(firebase.apps().map(app => app.delete()))

assertFails(pr: Promise) => Promise

如果輸入成功則返回一個 Promise,如果輸入被拒絕則返回成功。

使用它來斷言數據庫讀取或寫入失敗:

firebase.assertFails(app.database().ref("secret").once("value"));

assertSucceeds(pr: Promise) => Promise

如果輸入成功則返回一個 Promise,如果輸入被拒絕則返回一個 Promise。

使用它來斷言數據庫讀取或寫入成功:

firebase.assertSucceeds(app.database().ref("public").once("value"));

雲儲存

雲儲存

initializeTestApp({ storageBucket: string, auth: Object }) => FirebaseApp

使用它來創建一個作為特定用戶進行身份驗證的應用程序以在測試中使用。

返回與選項中指定的存儲桶名稱和身份驗證變量覆蓋相對應的已初始化 Firebase 應用程序。

firebase.initializeTestApp({
  storageBucket: "my-bucket",
  auth: { uid: "alice" }
});

initializeAdminApp({ storageBucket: string }) => FirebaseApp

使用它來創建一個經過管理員身份驗證的應用程序來設置測試狀態。

返回與選項中指定的存儲桶名稱相對應的已初始化管理 Firebase 應用程序。此應用程序在讀取和寫入存儲桶時繞過安全規則。

firebase.initializeAdminApp({ storageBucket: "my-bucket" });

loadStorageRules({ storageBucket: string, rules: Object }) => Promise

使用它來設置存儲桶的規則。

將規則發送到本地管理的存儲桶。接受一個選項對象,將“storageBucket”和“rules”指定為字符串。

firebase
      .loadStorageRules({
        storageBucket: "my-bucket",
        rules: fs.readFileSync("/path/to/storage.rules", "utf8")
      });

apps() => [FirebaseApp]

返回所有當前初始化的測試和管理應用程序。

使用它來在測試之間或之後清理應用程序(請注意,具有活動偵聽器的初始化應用程序會阻止 JavaScript 退出):

 Promise.all(firebase.apps().map(app => app.delete()))

assertFails(pr: Promise) => Promise

如果輸入成功則返回一個 Promise,如果輸入被拒絕則返回成功。

使用此斷言存儲桶讀取或寫入失敗:

firebase.assertFails(app.storage().ref("letters/private.doc").getMetadata());

assertSucceeds(pr: Promise) => Promise

如果輸入成功則返回一個 Promise,如果輸入被拒絕則返回一個 Promise。

使用它來斷言存儲桶讀取或寫入成功:

firebase.assertFails(app.storage().ref("images/cat.png").getMetadata());