Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

構建單元測試

在火力地堡本地仿真套件使之更容易充分驗證您的應用程序的功能和行為。它也是驗證 Firebase 安全規則配置的絕佳工具。使用 Firebase 模擬器在本地環境中運行和自動化單元測試。本文檔中概述的方法應該可以幫助您為應用程序構建和自動化單元測試以驗證您的規則。

如果你還沒有準備好,建立了火力地堡模擬器

運行模擬器之前

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

  • 仿真器將開始加載指定的規則firestore.rules或“storage.rules”你的領域firebase.json文件。如果該文件不存在,你不使用loadFirestoreRules如下描述或“loadStorageRules”的方法,仿真器將所有項目具有開放的規則。
  • 雖然大多數火力地堡的SDK與仿真器的工作直接,只有@firebase/rules-unit-testing庫支持嘲諷auth的安全規則,使單元測試更容易。此外,該庫還支持一些特定於模擬器的功能,例如清除所有數據,如下所列。
  • 模擬器還將接受通過客戶端 SDK 提供的生產 Firebase 身份驗證令牌並相應地評估規則,這允許在集成和手動測試中將您的應用程序直接連接到模擬器。

數據庫模擬器和生產之間的差異

  • 您不必顯式創建數據庫實例。模擬器將自動創建任何被訪問的數據庫實例。
  • 每個新數據庫都以封閉規則啟動,因此非管理員用戶將無法讀取或寫入。
  • 每個仿真數據庫應用火花計劃限制和配額(最值得注意的是,這限制每個實例100並發連接)。
  • 任何數據庫將接受字符串"owner"以管理員身份驗證令牌。
  • 模擬器目前沒有與其他 Firebase 產品的工作交互。值得注意的是,正常的 Firebase 身份驗證流程不起作用。相反,你可以使用initializeTestApp()的方法, rules-unit-testing庫,它需要一個auth領域。使用此方法創建的 Firebase 對象的行為就好像它已成功通過您提供的任何實體的身份驗證。如果您在通過null ,它會表現為未認證用戶( auth != null規則將失敗,例如)。

與實時數據庫模擬器交互

一個生產火力地堡實時數據庫實例是在一個子域訪問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.

導入後,實施單元測試包括:

  • 創建和配置RulesTestEnvironment有一個呼叫initializeTestEnvironment
  • 沒有觸發規則,使用便捷方法,它可以暫時繞過它們設置的測試數據, 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 ,它像一個的行為驗證認證用戶。通過返回的上下文創建的請求將附加一個模擬身份驗證令牌。或者,傳遞一個對象,為身份驗證令牌有效負載定義自定義聲明或覆蓋。

使用在測試中返回的測試上下文對象訪問配置的任何模擬器實例,包括那些配置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數據projectId配置用於公司的FireStore模擬器。

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

此方法獲取此測試上下文的 Firestore 實例。返回的 Firebase JS Client SDK 實例可與客戶端 SDK API(v9 模塊化或 v9 兼容)一起使用。

實時數據庫

實時數據庫

RulesTestEnvironment.clearDatabase() => Promise<void>

此方法清除在屬於實時數據庫數據projectId配置用於實時數據庫模擬器。

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

獲取此測試上下文的實時數據庫實例。返回的 Firebase JS Client SDK 實例可與客戶端 SDK API(v9 模塊化或 v9 兼容)一起使用。該方法接受實時數據庫實例的 URL。如果指定,則返回名稱空間的模擬版本的實例,其中包含從 URL 中提取的參數。

雲儲存

雲儲存

RulesTestEnvironment.clearStorage() => Promise<void>

此方法清除在屬於存儲分區對象和元數據projectId配置用於雲存儲仿真器。

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

此方法返回配置為連接到模擬器的 Storage 實例。該方法接受gs:// URL到火力地堡存儲桶用於測試。如果指定,則返回存儲桶名稱的模擬版本的 Storage 實例。

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

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

雲防火牆

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

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

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

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

使用它來創建一個被認證為特定用戶的應用程序以在測試中使用。

返回與選項中指定的存儲桶名稱和身份驗證變量覆蓋相對應的初始化 firebase 應用程序。

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

initializeAdminApp({ storageBucket: string }) => FirebaseApp

使用它來創建一個通過管理員身份驗證的應用程序來設置測試狀態。

返回與 options 中指定的存儲桶名稱相對應的初始化管理 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

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

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

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

雲防火牆

雲防火牆

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

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

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

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

使用它來創建一個被認證為特定用戶的應用程序以在測試中使用。

返回與選項中指定的存儲桶名稱和身份驗證變量覆蓋相對應的初始化 firebase 應用程序。

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

initializeAdminApp({ storageBucket: string }) => FirebaseApp

使用它來創建一個通過管理員身份驗證的應用程序來設置測試狀態。

返回與 options 中指定的存儲桶名稱相對應的初始化管理 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

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

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

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

assertSucceeds(pr: Promise) => Promise

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

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

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