I am working on tests for Cloud Firestore Rules, using mocha
and Firestore Emulator, and the question is how to initialize some test data before running tests?
To test my rules, I first need to initialize some test data. The problem is that I cannot put any data into a document when working with Emulator, documents only have id
.
I didn't find any example of setting up test data for Rules tests in the docs, so I tried to use both
makeDocumentSnapshot
from @firebase/testing
and document creation via admin app created with initializeAdminApp
.
Use case:
To get access to a document at /objects/{object_id}
, a User must be authenticated and have read
permission: get('/objects/{object_id}/users/{$(request.auth.uid)}').data.read == true
. Also, object
must be available: get('/objects/{object_id}').data.available == true
.
So, to test my rules I need some preset test data with User permissions.
Expected DB structure:
objects collection:
object_id: {
// document fields:
available (bool)
// nested collection:
users collection: {
user_id: {
// document fields:
read (bool)
}
}
}
Example of my rules:
service cloud.firestore {
match /databases/{database}/documents {
match /objects/{object} {
function objectAvailable() {
return resource.data.available;
}
// User has read access.
function userCanReadObject() {
return get(/databases/$(database)/documents/objects/$(object)/users/$(request.auth.uid)).data.read == true;
}
// Objects Permission Rules
allow read: if objectAvailable() && userCanReadObject();
allow write: if false;
// Access forbidden. Used for permission rules only.
match /users/{document=**} {
allow read, write: if false;
}
}
}
}
Example of my tests:
const firebase = require('@firebase/testing');
const fs = require('fs');
// Load Firestore rules from file
const firestoreRules = fs.readFileSync('../firestore.rules', 'utf8');
const projectId = 'test-application';
const test = require('firebase-functions-test')({ projectId, databaseName: projectId });
describe('Tests for Rules', () => {
let adminApp;
const testData = {
myObj: {
id: 'test',
data: {
available: true,
},
},
alice: {
id: 1,
data: {
read: true,
},
},
};
before(async () => {
// Load Rules
await firebase.loadFirestoreRules({ projectId, rules: firestoreRules });
// Initialize admin app.
adminApp = firebase.initializeAdminApp({ projectId }).firestore();
// Create test data
await adminApp.doc(`objects/${testData.myObj.id}`).set(testData.myObj.data);
await adminApp
.doc(`objects/${testData.myObj.id}/users/${testData.alice.id}`)
.set(testData.alice.data);
// Create test data with `firebase-functions-test`
// test.firestore.makeDocumentSnapshot(testData.myObj.data, `objects/${testData.myObj.id}`);
// test.firestore.makeDocumentSnapshot(
// testData.alice.data,
// `objects/${testData.myObj.id}/users/${testData.alice.id}`,
// );
});
beforeEach(async () => {
await firebase.clearFirestoreData({ projectId });
});
after(async () => {
// Shut down all testing Firestore applications after testing is done.
await Promise.all(firebase.apps().map(app => app.delete()));
});
describe('Testing', () => {
it('User with permission can read objects data', async () => {
const db = firebase
.initializeTestApp({ projectId, auth: { uid: testData.alice.id } })
.firestore();
const testObj = db.doc(`objects/${testData.myObj.id}`);
await firebase.assertSucceeds(testObj.get());
});
});
});
Console output for test run:
1) User with permission can read objects data
0 passing (206ms)
1 failing
1) Tests for Rules
Testing
User with permission can read objects data:
FirebaseError:
false for 'get' @ L53
To check created test data I added the following code before await firebase.assertSucceeds(testObj.get());
line:
const o = await adminApp.doc(`objects/${testData.myObj.id}`).get();
const u = await adminApp.doc(`objects/${testData.myObj.id}/users/${testData.alice.id}`).get();
console.log('obj data: ', o.id, o.data());
console.log('user data: ', u.id, u.data());
Output is the following:
obj data: test undefined
user data: 1 undefined
I also tried to remove the code from beforeEach
, the result is the same.
Connect Flutter to the Firestore Emulator On an Android emulator, you must point to the IP 10.0. 2.2 to connect to the 'localhost'. That's it! Your app will now use the local emulated database values instead of your real production data in the cloud.
Go to my local Firebase project path. Start the emulators using: firebase emulators:start. Create manually some mockup data using the GUI at http://localhost:4000/firestore using the buttons provided: + Start Collection and + Add Document. Export this data locally using: emulators:export ./mydirectory.
Cloud Firestore doesn't support native indexing or search for text fields in documents. Additionally, downloading an entire collection to search for fields client-side isn't practical. To enable full text search of your Cloud Firestore data, use a dedicated third-party search service.
You can use initializeAdminApp
to get admin privilegies (all operations are allowed):
const dbAdmin = firebase.initializeAdminApp({projectId}).firestore();
// Write mock documents
if (data) {
for (const key in data) {
if (data.hasOwnProperty(key)) {
const ref = dbAdmin.doc(key);
await ref.set(data[key]);
}
}
}
Data is supposed to have the following format:
data = {
'user/alice': {
name:'Alice'
},
'user/bob': {
name:'Bob'
},
};
You have to add data before you apply the rules.
Detailed information you can find here
const firebase = require('@firebase/testing');
const fs = require('fs');
let db
let projectId = `my-project-id-${Date.now()}`
async function setup(auth) {
const app = await firebase.initializeTestApp({
projectId: projectId,
auth: auth
});
db = app.firestore();
let data = {
'users/alovelace': {
first: 'Ada',
last: 'Lovelace'
}
}
// Add data before apply rules
for (const key in data) {
const ref = db.doc(key);
await ref.set(data[key]);
}
// Apply rules
await firebase.loadFirestoreRules({
projectId,
rules: fs.readFileSync('firestore.rules', 'utf8')
});
}
test('logged in', async () => {
await setup({ uid: "alovelace" })
let docRef = db.collection('users');
// check if there is data
let users = await docRef.get()
users.forEach(user => {
console.warn(user.id, user.data())
});
let read = await firebase.assertSucceeds(docRef.get());
let write = await firebase.assertFails(docRef.add({}));
await expect(read)
await expect(write)
});
afterAll(async () => {
Promise.all(firebase.apps().map(app => app.delete()))
});
firestore.rules
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read:if request.auth.uid != null;
allow write: if false
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With