Catch up on highlights from Firebase at Google I/O 2023. Learn more

構建單元測試

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

如果您還沒有設置 Firebase Emulators

運行模擬器之前

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

  • 模擬器最初會加載在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規則將失敗)。

與實時數據庫模擬器交互

可以在firebaseio.com的子域訪問生產 Firebase 實時數據庫實例,您可以像這樣訪問 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 ,其行為類似於經過身份驗證的身份驗證用戶。通過返回的上下文創建的請求將附加一個模擬身份驗證令牌。或者,傳遞一個定義自定義聲明或覆蓋身份驗證令牌有效負載的對象。

在測試中使用返回的測試上下文對象來訪問配置的任何模擬器實例,包括使用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 Auth 令牌。

在測試中使用返回的測試上下文對象來訪問配置的任何模擬器實例,包括使用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

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

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

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

返回與選項中指定的數據庫名稱和 auth 變量覆蓋相對應的初始化 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

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

將規則發送到本地運行的數據庫。採用一個選項對象,該對象將您的“數據庫名稱”和“規則”指定為字符串。

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

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

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

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

assertSucceeds(pr: Promise) => Promise

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

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

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

雲儲存

雲儲存

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

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

返回與選項中指定的存儲桶名稱和 auth 變量覆蓋相對應的初始化 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

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

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

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

assertSucceeds(pr: 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

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

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

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

返回與選項中指定的數據庫名稱和 auth 變量覆蓋相對應的初始化 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

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

將規則發送到本地運行的數據庫。採用一個選項對象,該對象將您的“數據庫名稱”和“規則”指定為字符串。

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

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

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

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

assertSucceeds(pr: Promise) => Promise

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

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

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

雲儲存

雲儲存

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

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

返回與選項中指定的存儲桶名稱和 auth 變量覆蓋相對應的初始化 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

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

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

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

assertSucceeds(pr: Promise) => Promise

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

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

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