简体   繁体   中英

How to mock data for testing Firestore with emulator

I am testing my Firestore security rules and have the following setup:

// 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
    ...

});

And my security rules file:

// 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 
  }
}

The test fails with:

FirebaseError: false for 'get' @ L*, Null value error. for 'get' @ L* FirebaseError: false for 'get' @ L*, Null value error. for 'get' @ L* , which occurs because I believe the function getUserData() returns null (same for userExists() ).

Instead, I expected getUserData() to return true because the document for user 'alice' is created based on the mock data.

Is it a problem with the Firestore emulator or is there something wrong with how I set up my mock data & test fixture? I used the Firebase simulator on the console to test the same rules against a real database and the rules work ( getUserData() and userExists() work as expected)

The code is largely based on this fireship tutorial and the official firebase unit testing tutorial

Turns out I had missed to see a crucial error in the logs letting me know that my user objects were actually not being written:

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

The fix was embarrassingly simple: add the name, phone number fields in the test fixture data to match the fields are enforced by the model/rules. That way, the get() and exists() functions would actually fetch against existing user data.

TL;DR - pay attention to the error and warning logs reported by the emulator when enforcing rules

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM