構建單元測試

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

如果您還沒有,請設置 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規則將失敗)。

與實時數據庫模擬器交互

可以在 firebaseio.com 的子域訪問生產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實現模擬身份驗證狀態的測試用例。

常用方法和實用函數

另請參閱v9 SDK 中特定於模擬器的測試方法

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

特定於模擬器的方法

另請參閱v9 SDK 中的常見測試方法和實用功能

雲防火牆

雲防火牆

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(v9 模塊化或 v9 兼容)一起使用。該方法接受實時數據庫實例的 URL。如果指定,則返回具有從 URL 提取的參數的命名空間的模擬版本的實例。

雲儲存

雲儲存

RulesTestEnvironment.clearStorage() => Promise<void>

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

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

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

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

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

雲防火牆

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,或者如果輸入被拒絕則該promise 成功。如果數據庫讀取或寫入失敗,請使用它來斷言。

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

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

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

返回與選項中指定的數據庫名稱和 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

如果輸入成功則返回一個 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

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

返回與選項中指定的存儲桶名稱和 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”和“規則”指定為字符串。

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 測試 SDK 用於與模擬器交互的方法。

雲防火牆

雲防火牆

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,或者如果輸入被拒絕則該promise 成功。如果數據庫讀取或寫入失敗,請使用它來斷言。

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

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

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

返回與選項中指定的數據庫名稱和 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

如果輸入成功則返回一個 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

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

返回與選項中指定的存儲桶名稱和 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”和“規則”指定為字符串。

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());