建構單元測試

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

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

執行模擬器前的準備

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

  • 模擬器一開始會載入 firebase.json 檔案 firestore.rulesstorage.rules 欄位中指定的規則。如果檔案不存在,且您未使用下方所述的 loadFirestoreRulesloadStorageRules 方法,模擬器會將所有專案視為具有開放規則。
  • 雖然大多數 Firebase SDK 都能直接與模擬器搭配使用,但只有 @firebase/rules-unit-testing 程式庫支援模擬安全性規則中的 auth,因此可大幅簡化單元測試。此外,程式庫還支援幾項模擬器專屬功能,例如清除所有資料,如下所示。
  • 模擬器也會接受透過 Client 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>

使用 JavaScript SDK 第 9 版執行本機單元測試

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
  • 設定測試套件和每次測試前後的掛鉤,並呼叫 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 資料庫中屬於 projectId 的資料,這些資料已設定為 Firestore 模擬器。

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 以上) 使用。這個方法會接受即時資料庫執行個體的網址。如果已指定,則會傳回命名空間模擬版本的執行個體,並從網址中擷取參數。

Cloud Storage

Cloud Storage

RulesTestEnvironment.clearStorage() => Promise<void>

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

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

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

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

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

Cloud Firestore

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

這個方法會傳回與選項中指定的專案 ID 和驗證變數對應的已初始化 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

這個方法會傳回 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

您可以使用這項功能建立已驗證為特定使用者的應用程式,以用於測試。

傳回與選項中指定的資料庫名稱和驗證變數覆寫項目對應的已初始化 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

Returns a promise that is rejected if the input succeeds and succeeds if the input is rejected.

使用這項功能,確認資料庫讀取或寫入作業失敗:

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

您可以使用這項功能建立已驗證為特定使用者的應用程式,以用於測試。

傳回對應於儲存空間 bucket 名稱的已初始化 Firebase 應用程式,以及選項中指定的驗證變數覆寫。

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

initializeAdminApp({ storageBucket: string }) => FirebaseApp

您可以使用這個函式建立經過管理員身分驗證的應用程式,以便為測試設定狀態。

傳回與選項中指定的儲存空間值區名稱對應的已初始化管理員 Firebase 應用程式。這個應用程式在讀取及寫入 Bucket 時,會略過安全規則。

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

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

您可以使用這項功能設定儲存空間 bucket 的規則。

將規則傳送至本機管理的儲存空間值區。這個函式會採用選項物件,將「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

Returns a promise that is rejected if the input succeeds and succeeds if the input is rejected.

使用這個方法,確認儲存空間值區的讀取或寫入作業失敗:

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

assertSucceeds(pr: Promise) => Promise

Returns a promise that succeeds if the input succeeds and is rejected if the input is rejected.

使用這項功能,確認儲存空間 bucket 的讀取或寫入作業成功:

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

這個方法會傳回與選項中指定的專案 ID 和驗證變數對應的已初始化 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

這個方法會傳回 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

您可以使用這項功能建立已驗證為特定使用者的應用程式,以用於測試。

傳回與選項中指定的資料庫名稱和驗證變數覆寫項目對應的已初始化 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

Returns a promise that is rejected if the input succeeds and succeeds if the input is rejected.

使用這項功能,確認資料庫讀取或寫入作業失敗:

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

您可以使用這項功能建立已驗證為特定使用者的應用程式,以用於測試。

傳回對應於儲存空間 bucket 名稱的已初始化 Firebase 應用程式,以及選項中指定的驗證變數覆寫。

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

initializeAdminApp({ storageBucket: string }) => FirebaseApp

您可以使用這個函式建立經過管理員身分驗證的應用程式,以便為測試設定狀態。

傳回與選項中指定的儲存空間值區名稱對應的已初始化管理員 Firebase 應用程式。這個應用程式在讀取及寫入 Bucket 時,會略過安全規則。

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

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

您可以使用這項功能設定儲存空間 bucket 的規則。

將規則傳送至本機管理的儲存空間值區。這個函式會採用選項物件,將「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

Returns a promise that is rejected if the input succeeds and succeeds if the input is rejected.

使用這個方法,確認儲存空間值區的讀取或寫入作業失敗:

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

assertSucceeds(pr: Promise) => Promise

Returns a promise that succeeds if the input succeeds and is rejected if the input is rejected.

使用這項功能,確認儲存空間 bucket 的讀取或寫入作業成功:

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