建構單元測試

Firebase Local Emulator Suite可讓您更輕鬆地全面驗證應用程式的功能和行為。這也是驗證 Firebase Security Rules 設定的絕佳工具。使用 Firebase 模擬器在本機環境中執行及自動化單元測試。本文檔中概述的方法應該可以幫助您建立和自動化應用程式的單元測試,以驗證您的Rules

如果尚未設定 Firebase 模擬器,請先完成這項作業。

執行模擬器前的準備事項

開始使用模擬器前,請注意下列事項:

  • 模擬器一開始會載入 firebase.json 檔案 firestore.rulesstorage.rules 欄位中指定的規則。如果檔案不存在,且您未使用下文所述的 loadFirestoreRulesloadStorageRules 方法,模擬器會將所有專案視為具有開放規則。
  • 雖然大多數 Firebase SDK 都能直接搭配模擬器使用,但只有 @firebase/rules-unit-testing 程式庫支援模擬安全性規則中的 auth,因此可大幅簡化單元測試。此外,程式庫還支援幾項模擬器專屬功能,例如清除所有資料,如下所示。
  • 模擬器也會接受透過用戶端 SDK 提供的正式版 Firebase Auth 權杖,並據此評估規則,因此您可以在整合和手動測試中,將應用程式直接連線至模擬器。

資料庫模擬器與正式環境的差異

  • 您不必明確建立資料庫執行個體。模擬器會自動建立任何存取的資料庫執行個體。
  • 每個新資料庫都會從封閉規則開始,因此非管理員使用者無法讀取或寫入。
  • 每個模擬資料庫都套用 Spark 計劃 的限制和配額(最值得注意的是,這將每個實例限制為 100 個並發連接)。
  • 任何資料庫都會接受字串 "owner" 做為管理員驗證權杖。
  • 模擬器目前無法與其他 Firebase 產品互動。請注意,正常的 Firebase 驗證流程無法運作。 請改用 rules-unit-testing 程式庫中的 initializeTestApp() 方法,該方法會採用 auth 欄位。使用這個方法建立的 Firebase 物件,行為就好像已成功驗證您提供的任何實體。若傳入 null,則其行為將如同未經驗證的使用者(例如,auth != null 規則將會失敗)。

Realtime Database 模擬器互動

您可以在 Realtime Database 的子網域存取正式版 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
} 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
  • 設定測試資料而不觸發 Rules,使用允許您暫時繞過它們的便捷方法,RulesTestEnvironment.withSecurityRulesDisabled
  • 設定測試套件和每個測試的 before/after 鉤子,呼叫清理測試資料和環境,例如 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 使用者。透過傳回的上下文建立的請求將附加一個模擬的 Authentication 令牌。(選用) 傳遞定義自訂聲明或覆寫 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,其行為類似於未透過 Authentication 登入的客戶端。透過傳回的內容建立要求時,不會附加 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。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'), { ... });

模擬器專屬方法

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

Cloud Firestore

Cloud Firestore

RulesTestEnvironment.clearFirestore() => Promise<void>

此方法清除 Firestore 資料庫中屬於為 Firestore 模擬器配置的 projectId 的資料。

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

此方法取得此測試上下文的 Firestore 實例。傳回的 Firebase JS Client SDK 執行個體可用於 Client SDK API (v9 模組化或 v9 相容)。

Realtime Database

Realtime Database

RulesTestEnvironment.clearDatabase() => Promise<void>

這個方法會清除 Realtime Database 中屬於為 Realtime Database 模擬器設定的 projectId 資料。

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

取得此測試上下文的 Realtime Database 實例。傳回的 Firebase JS 用戶端 SDK 實例可以與客戶端 SDK API(模組化或命名空間,版本 9 或更高版本)一起使用。此方法接受即時資料庫執行個體的 URL。如果指定,則傳回一個模擬命名空間的實例,參數從 URL 中提取。

Cloud Storage

Cloud Storage

RulesTestEnvironment.clearStorage() => Promise<void>

這個方法會清除 projectId 所屬儲存空間值區中的物件和中繼資料,這些值區已為 Cloud Storage 模擬器設定。

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

此方法傳回一個配置為連接到模擬器的 Storage 實例。 此方法接受一個指向 Firebase Storage Bucket 的 gs:// URL 來進行測試。如果指定,則傳回 bucket 名稱模擬版本的 Storage 執行個體。

使用 v8 JavaScript SDK 執行本機單元測試

選取產品,即可查看 Firebase Test SDK 用來與模擬器介接的方法。

Cloud Firestore

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

此方法傳回一個已初始化的 Firebase 應用,該應用程式與選項中指定的項目 ID 和 auth 變數相對應。使用此功能可以創建一個以特定用戶身份進行身份驗證的應用程序,以便在測試中使用。

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

如果輸入內容成功,這個方法會傳回遭拒的 Promise;如果輸入內容遭拒,則會傳回成功的 Promise。用來判斷資料庫讀取或寫入作業是否失敗。

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

assertSucceeds(pr: Promise) => 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"
});
   

Realtime Database

Realtime Database

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

您可以使用這個選項設定資料庫規則。

將規則傳送至在本機執行的資料庫。接受選項物件,以字串形式指定「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,如果輸入成功則傳回被拒絕的 Promise,如果輸入被拒絕則回傳成功。

使用此功能可以斷言資料庫讀取或寫入操作失敗:

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

assertSucceeds(pr: Promise) => Promise

如果輸入內容成功,就會傳回成功狀態的 Promise;如果輸入內容遭到拒絕,就會傳回遭拒狀態的 Promise。

使用此功能可斷言資料庫讀取或寫入操作成功:

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

Cloud Storage

Cloud Storage

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

使用此功能設定儲存桶的規則。

將規則傳送到本地管理的儲存桶。接受一個選項對象,該對像以字串形式指定“存儲桶”和“規則”。

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,如果輸入成功則傳回被拒絕的 Promise,如果輸入被拒絕則回傳成功。

使用這個方法,即可判斷儲存空間 bucket 的讀取或寫入作業是否失敗:

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

assertSucceeds(pr: Promise) => Promise

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

使用此功能可斷言儲存桶的讀取或寫入操作是否成功:

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

適用於 JS SDK v8 的 RUT 庫 API

選擇產品以查看 Firebase 測試 SDK 與模擬器互動所使用的方法。

Cloud Firestore

Cloud Firestore

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

此方法傳回一個已初始化的 Firebase 應用,該應用程式與選項中指定的項目 ID 和 auth 變數相對應。使用此功能可以創建一個以特定用戶身份進行身份驗證的應用程序,以便在測試中使用。

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

如果輸入內容成功,這個方法會傳回遭拒的 Promise;如果輸入內容遭拒,則會傳回成功的 Promise。用來判斷資料庫讀取或寫入作業是否失敗。

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

assertSucceeds(pr: Promise) => 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"
});
   

Realtime Database

Realtime Database

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

您可以使用這個選項設定資料庫規則。

將規則傳送至在本機執行的資料庫。接受選項物件,以字串形式指定「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,如果輸入成功則傳回被拒絕的 Promise,如果輸入被拒絕則回傳成功。

使用此功能可以斷言資料庫讀取或寫入操作失敗:

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

assertSucceeds(pr: Promise) => Promise

如果輸入內容成功,就會傳回成功狀態的 Promise;如果輸入內容遭到拒絕,就會傳回遭拒狀態的 Promise。

使用此功能可斷言資料庫讀取或寫入操作成功:

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

Cloud Storage

Cloud Storage

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

使用此功能設定儲存桶的規則。

將規則傳送到本地管理的儲存桶。接受一個選項對象,該對像以字串形式指定“存儲桶”和“規則”。

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,如果輸入成功則傳回被拒絕的 Promise,如果輸入被拒絕則回傳成功。

使用這個方法,即可判斷儲存空間 bucket 的讀取或寫入作業是否失敗:

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

assertSucceeds(pr: Promise) => Promise

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

使用此功能可斷言儲存桶的讀取或寫入操作是否成功:

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