测试安全规则

构建应用期间,您也许应该锁定对自己的 Firestore 数据库的访问。不过,在发布之前,您需要设置更精细的 Firestore 安全规则。借助 Firestore 模拟器,您可以编写单元测试,以便检查 Firestore 安全规则的行为。

快速入门

如需了解一些设置了简单规则的基本测试用例,请试看 JavaScript 快速入门TypeScript 快速入门

了解 Firestore 安全规则

使用移动和 Web 客户端库时,您可以实现 Firebase 身份验证Firestore 安全规则来处理无服务器的身份验证、授权和数据验证。

Firestore 安全规则包含两部分:

  1. match 语句:用于识别数据库中的文档。
  2. allow 表达式:用于控制对这些文档的访问权限。

Firebase 身份验证能够验证用户的凭据,为基于用户和角色的访问权限系统奠定基础。

系统会遵照您的安全规则,评估来自 Firestore 移动/Web 客户端库的每个数据库请求,然后才会允许读取或写入数据。如果规则拒绝了对任何指定文档路径的访问,则整个请求将会失败。

要详细了解 Firestore 安全规则,请参阅开始使用 Firestore 安全规则

安装模拟器

要安装 Firestore 模拟器,请使用 Firebase CLI 并运行以下命令:

firebase setup:emulators:firestore

运行模拟器

使用以下命令启动模拟器。模拟器将一直运行,直到您终止相应进程为止:

firebase emulators:start --only firestore

在多数情况下,您需要启动模拟器,运行测试套件,然后在测试运行后关闭模拟器。您可以使用 emulators:exec 命令轻松完成这些操作:

firebase emulators:exec --only firestore "./my-test-script.sh"

模拟器启动后将尝试在默认端口 (8080) 上运行。您可以通过修改 firebase.json 文件中的 "emulators" 部分来更改模拟器端口:

{
  // ...
  "emulators": {
    "firestore": {
      "port": "YOUR_PORT"
    }
  }
}

运行模拟器之前的注意事项

开始使用模拟器之前,请注意以下几点:

  • 模拟器将会首先加载您在 firebase.json 文件的 firestore.rules 字段中指定的规则。它需要使用包含 Firestore 安全规则的本地文件的名称,并将这些规则应用于所有项目。如果您未提供本地文件路径或按照如下所述调用 loadFirestoreRules 方法,模拟器会将所有项目视为采用开放规则。
  • 尽管很多 SDK 都支持模拟器,但只有 @firebase/testing Node.js 模块支持模拟安全规则中的 auth,这样更便于进行单元测试。此外,该模块还支持几项特定于模拟器的功能(例如清除所有数据),具体如下所示。
  • 模拟器还将接受通过客户端 SDK 提供的生产 Firebase 身份验证令牌,并据此评估规则,从而可以在集成和手动测试中将您的应用直接连接到模拟器。

运行本地测试

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。使用此方法判断数据库读取或写入是否失败。

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

生成测试报告

运行一系列测试后,您可以访问测试范围报告,其中显示了每条安全规则的评估结果。

如需获取该报告,请在模拟器运行时查询其上的公开端点。对于适合浏览器的版本,请使用以下网址:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

这会将您的规则分解为表达式和子表达式,您可以将鼠标悬停在相应表达式上以了解更多信息(包括评估次数和返回的值)。如需这些数据的原始 JSON 版本,请在查询中包含以下网址:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

模拟器和生产数据库的区别

  1. 您不必明确地创建 Firestore 项目。模拟器会自动创建实例供访问。
  2. Firestore 模拟器不适用于常规 Firebase 身份验证流程。不过,我们在 Firebase Test SDK 测试模块中提供了 initializeTestApp() 方法,该方法接受 auth 字段。使用此方法创建的 Firebase 句柄的行为将如同它已经以您提供的任何实体身份成功通过身份验证一样。如果您传入 null,其行为将与未经身份验证的用户相同(例如,auth != null 规则将失败)。

排查已知问题

在使用 Firestore 模拟器时,您可能会遇到以下已知问题。请按照以下指导来排查您遇到的任何不正常行为。这些注释是使用 Firebase Test SDK 编写的,但这些常规方法适用于任何 Firebase SDK。

测试行为不一致

在未对测试本身进行任何更改的情况下,如果您的测试时而通过时而失败,您可能需要确认它们有正确的排序。与模拟器的大多数交互都是异步的,因此请仔细检查所有异步代码都有正确的排序。您可以通过链接 Promise 或按需要使用 await 记号来解决排序问题。

尤其应检查以下异步操作:

  • 设置安全规则,例如使用 firebase.loadFirestoreRules
  • 读取和写入数据,例如使用 db.collection("users").doc("alice").get()
  • 操作断言,包括 firebase.assertSucceedsfirebase.assertFails

测试仅在第一次加载模拟器时通过

模拟器是有状态的。它将写入其中的所有数据都存储在内存中,因此每当模拟器关闭时,所有数据都会丢失。如果您针对相同的项目 ID 运行多个测试,则每个测试都会生成可能影响后续测试的数据。您可以使用以下任一方法避免这一问题的影响:

  • 为每个测试使用唯一的项目 ID。请注意,如果您选择执行此操作,则需要在每个测试中调用 loadFirestoreRules;规则仅针对默认项目 ID 自动加载。
  • 重新构建您的测试,使它们不与以前写入的数据交互,例如,为每个测试使用不同的集合。
  • 删除测试期间写入的所有数据。

测试设置非常复杂

您可能想要测试 Firestore 安全规则实际上不允许的场景。例如,测试未经身份验证的用户是否可以修改数据就很难实现,因为未经身份验证的用户无法修改数据。

如果您的规则使测试设置变得复杂,请尝试使用管理员授权的客户端来绕过规则。您可以使用 firebase.initializeAdminApp 执行此操作。管理员授权的客户端执行的读取和写入操作会绕过规则,并且不会触发 PERMISSION_DENIED 错误。