如何對 Firebase Security Rules 做單元測試?
本文概述如何用第九版 JavaScript SDK 和模擬器為應用程式建立和自動化單元測試來驗證 Security Rules。如果還沒有設定 Firebase 模擬器,請設定它。
第一步: 使用 Firebase v9
import {
assertFails,
assertSucceeds,
initializeTestEnvironment
} from "@firebase/rules-unit-testing"
第二步: 建立測試環境
透過呼叫initializeTestEnvironment
建立和配置 RulesTestEnvironment
。
let testEnv = await initializeTestEnvironment({
projectId: "demo-project-1234",
firestore: {
rules: fs.readFileSync("firestore.rules", "utf8"),
}
});
第三部:設定測試資料
這一步使用 RulesTestEnvironment.withSecurityRulesDisabled()
,它用來在測試期間臨時禁用安全規則。這樣的設置允許開發者在進行單元測試或集成測試時,不受安全規則的限制,能夠更自由地測試應用的各個部分。
第四步: Unit Test Setup and TearDown
在設定測試套件時,通常會包括在每個測試執行前後進行特定的動作,這稱為測試的前置和後置掛鉤(hooks)。這些掛鉤的功能是為了確保測試的環境在開始前是乾淨的,以及測試完成後將環境恢復到一個基線狀態。這樣做可以避免測試間的數據污染問題,並保證每個測試的獨立性和可重複性。
RulesTestEnvironment.cleanup()
:這個方法用於測試結束後清理測試環境,移除所有臨時設置和數據,確保不會對後續的測試造成干擾。RulesTestEnvironment.clearFirestore()
:這個方法專門用於清空 Firestore 的所有文檔和數據集合,是在需要完全重置數據庫狀態時使用。
第五部:實作模擬驗證狀態的 unit test
這一步使用RulesTestEnvironment.authenticatedContext
RulesTestEnvironment.unauthenticatedContext
實作模擬驗證狀態的測試案例。
使用者驗證後的測試環境
產生驗證某個使用者的 Rule 測試環境
iimport { setDoc } from "firebase/firestore";
const alice = testEnv.authenticatedContext("alice", { … });
// Use the Firestore instance associated with this context
await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });
上面這個範例,會模擬 alice
登入後,並且對 /users/alice
寫入一個 document。
使用者未登入的測試環境
// 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));
斷言
如同其他 unit test 框架, firebase rule testing 也提供兩個斷言供開發者使用
assertSucceeds(pr: Promise<any>)) => Promise<any>
:: 確認成功assertFails(pr: Promise<any>)) => Promise<any>
:: 確認失敗
考慮一個使用 Cloud Firestore 的範例應用,該應用程式會計算使用者點擊按鈕的次數。該應用程式採用以下規則:
service cloud.firestore {
match /databases/{database}/documents {
match /counters/{counter} {
allow read;
allow write: if request.resource.data.value == resource.data.value +1;
}
}
}
若要偵錯上面顯示的規則中的錯誤,請使用下列範例 JavaScript 測試:
const counter0 = db.collection("counters").doc("0");await firebase.assertSucceeds(counter0.set({value: 0}));
接下來來看一個完整的範例,了解怎麼在 vitest
進行 security rules 的測試。Vitest 是一個現代的 JavaScript 測試框架。Vitest 能夠直接運行 ES 模組源碼而無需轉換。此外,Vitest 還支持模擬、快照測試等高級功能,並且可以透過插件輕鬆整合其他工具,如 React Testing Library。
import { afterAll, beforeAll, describe, it } from "vitest";
import fs from "fs";
import {
RulesTestEnvironment,
assertFails,
assertSucceeds,
initializeTestEnvironment
} from "@firebase/rules-unit-testing"
import { doc, getDoc } from "firebase/firestore";
describe("Firestore rules", () => {
let testEnv: RulesTestEnvironment;
// 在所有測試開始之前,設定測試環境
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: "demo-makerkit",
firestore: {
rules: fs.readFileSync(__dirname + "/firestore.rules", "utf8"),
host: "localhost",
port: 8080,
},
});
});
// 在所有測試結束後,清除 Firestore 數據
afterAll(async () => {
testEnv.clearFirestore();
});
// 測試案例:確認未認證的使用者不可讀取數據
it("should allow read if authenticated", async () => {
const alice = testEnv.authenticatedContext("VdoROdrhNMFSGktYdEOzbQOKndnd", {});
const docRef = doc(alice.firestore(), '/organizations/vrpMguv6cwf7L9KLEknR');
return assertSucceeds(getDoc(docRef));
});
// 測試案例:確認已認證的使用者可以讀取數據
it("should not allow read if not authenticated", async () => {
const alice = testEnv.unauthenticatedContext();
const docRef = doc(alice.firestore(), '/organizations/vrpMguv6cwf7L9KLEknR');
return assertFails(getDoc(docRef));
});
});
其他的 unit test framework 也是差不多的概念。
第六步:生成測試報告
Firebase 模擬器能夠產生規則覆蓋率報告,使用以下 URL 閱讀:
http://localhost:8080/emulator/v1/projects/<database_name>:ruleCoverage.html