如何模拟数据以使用模拟器测试Firestore

时间:2020-07-17 11:50:53

标签: javascript firebase google-cloud-firestore mocking firebase-security

我正在测试Firestore安全规则,并具有以下设置:

// firestore.spec.js
/**
 * Creates a new client FirebaseApp with authentication and returns the Firestore instance.
 * Also optionally seeds the project with mock data
 */
setupFirestoreDb = async (auth, data) => {
  // initialize test app
  const PROJECT_ID = "my-test-project-xyz";
  const app = firebase.initializeTestApp({ projectId: PROJECT_ID, auth });
  const db = app.firestore();

  // Write mock documents
  if (data) {
    for (const key in data) {
      const ref = db.doc(key);
      ref.set(data[key]);
    }
  }

  return db;
};

beforeEach(async () => {
  // Clear the database between tests
  await firebase.clearFirestoreData({ projectId: PROJECT_ID });
});

before(async () => {
  // Load the rules file before the tests begin
  const rules = fs.readFileSync("firestore.rules", "utf8");
  await firebase.loadFirestoreRules({ projectId: PROJECT_ID, rules });
});

after(async () => {
  // Delete all the FirebaseApp instances created during testing
  // Note: this does not affect or clear any data
  await Promise.all(firebase.apps().map((app) => app.delete()));
});

const mockData = {
  'users/alice': {
    foo: 'bar',
    nestedData: {
      baz: 'fae'
    }
  },
  'users/bob': {
    foo: 'bar',
    nestedData: {
      baz: 'fae'
    }
  },
  // ... more data
}

// run the test suite
describe("Test Security Rules", () => {

  it("should let any signed-in user to read any public profile", async () => {
    let db = await setupFirestoreDb({uid: "bob"}, mockData);
    aliceRef = db.collection("users").doc("alice");
    await firebase.assertSucceeds(aliceRef.get()); // fails
  });

    // more test cases
    ...

});

我的安全规则文件:

// firestore.rules
rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    // define common functions used across collections/documents

    function userExists() {
      return exists(/databases/$(database)/documents/users/$(request.auth.uid));
    }

    // Fetch a user from Firestore by UID
    function getUserData(uid) {
      return get(/databases/$(database)/documents/users/$(uid)).data
    }

    function isValidUser() {
      let userData = request.resource.data;
      return userData.name != null && userData.phoneNumber != null;
    }

    // ... more functions

    // lock the entire database by default
    match /{document=**} {
      allow read, write: if false;
    }

    // rules for users collection
    match /users/{userId} {
      allow read: if isSignedIn() && getUserData(userId) != null; // && userExists(userId); // make sure user being read exists
      allow write: if isSignedIn() && isUser(userId) && isValidUser(); // only an authenticated user can create own account

      // ... other rules for nested data (subcollections)
    }

    // ... more rules 
  }
}

测试失败,并显示:

FirebaseError: false for 'get' @ L*, Null value error. for 'get' @ L*,是因为我相信函数getUserData()返回null(与userExists()相同)。

相反,我希望getUserData()返回true,因为基于模拟数据创建了用户“ alice”的文档。

Firestore模拟器是否有问题,或者我如何设置模拟数据和测试装置是否有问题?我在控制台上使用Firebase模拟器对真实数据库测试了相同的规则,并且这些规则正常工作(getUserData()userExists()正常工作)

代码主要基于此fireship tutorial和官方火力场unit testing tutorial

1 个答案:

答案 0 :(得分:1)

结果是我错过了在日志中看到一个严重错误的信息,让我知道实际上没有在写用户对象:

(node:25284) UnhandledPromiseRejectionWarning: FirebaseError: 7 PERMISSION_DENIED:
false for 'create' @ L*, Property phoneNumber is undefined on object. for 'create' @ L*

修复非常简单:在测试夹具数据中添加名称,电话号码字段以匹配字段,这些字段由模型/规则强制执行。这样,get()exists()函数实际上将针对现有的用户数据进行获取。

TL; DR-执行规则时,请注意模拟器报告的错误和警告日志