转到控制台

测试 Cloud Firestore 安全规则

构建应用期间,您可能会直接关闭对 Cloud Firestore 数据库的访问。但要发布应用时,简单关闭访问不再可行,您需要在发布前设置更精细的 Cloud Firestore 安全规则。借助 Cloud Firestore 模拟器,您可以编写单元测试,以便检查 Cloud Firestore 安全规则的行为。

快速入门

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

了解 Cloud Firestore 安全规则

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

Cloud Firestore 安全规则包含两部分:

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

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

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

请阅读开始使用 Cloud Firestore 安全规则,详细了解 Cloud Firestore 安全规则。

安装模拟器

要安装 Cloud Firestore 模拟器,请使用 Firebase CLI 并按照以下步骤操作。

  1. 安装 Cloud Firestore 模拟器:

     firebase setup:emulators:firestore
     

  2. 使用以下命令启动模拟器。模拟器将在您进行各项测试时运行。

     firebase serve --only firestore
     

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

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

  • 目前唯一支持模拟器的 SDK 是 Node.js SDK。为了让与模拟器的交互更方便,我们提供了 @firebase/testing 模块。
  • 模拟器支持可选的 --rules CLI 标志。此标志需要使用包含 Cloud Firestore 安全规则的本地文件的名称,以便将这些规则应用到所有项目。如果您未提供本地文件路径或使用如下所述的 loadFirestoreRules 方法,则模拟器会将所有项目视为采用开放规则。

运行本地测试

使用 @firebase/testing 模块与本地运行的模拟器交互。如果您遇到超时或 ECONNREFUSED 错误,请仔细检查模拟器是否确实正在运行。

我们强烈建议使用最新版本的 Node.js,以便您能够使用 async/await 表示法。几乎所有您可能希望测试的行为都涉及异步函数,而测试模块可与基于 Promise 的代码搭配使用。

该模块提供了以下方法:

  • initializeTestApp({ projectId: <name>, auth: <auth> }) => FirebaseApp

    此方法会返回一个初始化的 Firebase 应用,该应用与选项中指定的项目 ID 和 auth 变量相对应。使用此方法可创建以特定用户身份通过了身份验证的应用以用于测试。

     firebase.initializeTestApp({
       projectId: "my-test-project",
       auth: { uid: "alice", email: "alice@example.com" }
     });
    
  • initializeAdminApp({ projectId: <name> }) => FirebaseApp

    此方法会返回一个初始化的 Firebase 管理应用。此应用执行读取和写入操作时会绕过安全规则。使用此方法可创建以管理员身份通过了身份验证的应用以设置测试状态。

    firebase.initializeAdminApp({ projectId: "my-test-project" });
    
  • apps() => [FirebaseApp] 此方法会返回目前已初始化的所有测试应用和管理应用。使用此方法可在各次测试之间或测试之后清理应用。

     Promise.all(firebase.apps().map(app => app.delete()))
    
  • loadFirestoreRules({ projectId: <name>, rules: <rules> }) => 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: <name> }) => 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. 您不必明确地创建 Cloud Firestore 项目。模拟器会自动创建实例供访问。
  2. Cloud Firestore 模拟器不会与其他 Firebase 产品进行有效的互动。尤其值得注意的是,标准的 Firebase 身份验证流程将无法进行。不过,我们在测试模块中提供了 initializeTestApp() 方法,该方法会接受 auth 字段。使用此方法创建的 Firebase 手柄的行为就好像它已作为您提供的任何实体成功通过了身份验证一样。如果您传入 null,它的行为将与未经过身份验证的用户相同(例如,auth != null 规则将失败)。
  3. Cloud Firestore 模拟器会持续保留数据。这可能会影响您的结果。要独立运行测试,请为每个独立测试分配不同的项目 ID。当您调用 firebase.initializeAdminAppfirebase.initializeTestApp 时,请将唯一 ID、时间戳或随机整数附加到 projectID

排查已知问题

在使用 Cloud Firestore 模拟器时,您可能会遇到以下已知问题。请按照以下指导来排查您遇到的任何不正常行为。

测试行为不一致

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

尤其应检查以下异步操作: - 设置安全规则,例如,使用 firebase.loadFirestoreRules 进行设置。 - 读取和写入数据,例如,使用 db.collection("users").doc("alice").get() 进行读写。 - 操作断言,包括 firebase.assertSucceedsfirebase.assertFails

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

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

  • 为每个测试使用唯一的项目 ID。
  • 重新构建您的测试,使它们不与以前写入的数据交互,例如,为每个测试使用不同的集合。
  • 删除测试期间写入的所有数据。

测试设置非常复杂

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

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