建構單元測試

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

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

執行模擬器前

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

  • 模擬器一開始會載入 firebase.json 檔案的 firestore.rules 或「storage.rules」欄位中指定的規則。如果檔案不存在,且您未使用 loadFirestoreRules 或下方所述的「loadStorageRules」方法,模擬器會將所有專案視為具有開放規則。
  • 雖然大多數 Firebase SDK 會直接與模擬器搭配運作,但只有 @firebase/rules-unit-testing 程式庫支援模擬安全性規則中的 auth,因此單元測試會變得更加簡單。此外,此程式庫支援一些模擬器專屬功能,例如清除所有資料,如下所列。
  • 模擬器也會接受透過用戶端 SDK 提供的正式版 Firebase 驗證權杖,並據此評估規則,讓您在整合和手動測試時,直接將應用程式連結至模擬器。

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

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

Realtime Database 模擬器互動

您可以在 firebaseio.com 的子網域中存取正式版 Firebase Realtime Database 例項,並透過以下方式存取 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 符號。您可能想要測試的幾乎所有行為都涉及非同步函式,而測試模組則是為了與以承諾為基礎的程式碼搭配使用而設計。

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
  • 設定測試套件和個別測試前/後鉤子,並透過呼叫清理測試資料和環境,例如 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(), '/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。承諾解析 / 拒絕後,系統就會銷毀這個內容。

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 例項可搭配用戶端 SDK API (第 9 版模組化或第 9 版相容性) 使用。

Realtime Database

Realtime Database

RulesTestEnvironment.clearDatabase() => Promise<void>

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

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

為此測試情境取得 Realtime Database 例項。傳回的 Firebase JS Client SDK 例項可搭配用戶端 SDK API (模組化或命名空間,版本 9 以上) 使用。此方法會接受即時資料庫例項的網址。如果指定,則會傳回命名空間模擬版本的例項,其中包含從網址擷取的參數。

Cloud Storage

Cloud Storage

RulesTestEnvironment.clearStorage() => Promise<void>

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

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

這個方法會傳回已設定為連線至模擬器的 Storage 例項。此方法會接受 gs:// 至 Firebase Storage Bucket 的網址,以便進行測試。如果指定,則會傳回值區名稱模擬版本的 Storage 例項。

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

選取產品,查看 Firebase Test SDK 用於與模擬器連結的方法。

Cloud Firestore

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

這個方法會傳回已初始化的 Firebase 應用程式,對應至選項中指定的專案 ID 和驗證變數。您可以使用這項功能,建立以特定使用者身分驗證的應用程式,用於測試。

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"
});
   

Realtime Database

Realtime Database

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

傳回的應許會在輸入成功時遭到拒絕,在輸入遭到拒絕時則會成功。

您可以使用這項方法,斷言資料庫讀取或寫入作業失敗:

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

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

您可以使用這個值來設定儲存值區的規則。

將規則傳送至本機管理的儲存空間值區。接收指定「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 用於與模擬器連結的方法。

Cloud Firestore

Cloud Firestore

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

這個方法會傳回已初始化的 Firebase 應用程式,對應至選項中指定的專案 ID 和驗證變數。您可以使用這項功能,建立以特定使用者身分驗證的應用程式,用於測試。

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"
});
   

Realtime Database

Realtime Database

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

傳回的應許會在輸入成功時遭到拒絕,在輸入遭到拒絕時則會成功。

您可以使用這項方法,斷言資料庫讀取或寫入作業失敗:

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

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

您可以使用這個值來設定儲存值區的規則。

將規則傳送至本機管理的儲存空間值區。接收指定「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());