Firestore rules testing on a custom port
2 min read

Firestore rules testing on a custom port

A Firestore emulator running on a custom port needs some specific settings when testing rules.

When working with Firebase, it is possible to emulate Firestore, and other Firebase services, on your local machine, so that you do not need to be connected to your production project, and, more importantly, you do not risk to change or delete data used in production.

In some situation you need to run the Firebase emulators on a different port than the default one.

You can do that by setting the port in firebase.json:

{
    "firestore": {
        "rules": "firestore.rules",
        "indexes": "firestore.indexes.json"
    },
    "emulators": {
        "firestore": {
            "port": 65535
            }
    }
}

then, in your app, you need to set the host in your Firestore.instance.settings to localhost:65535, and sslEnabled: false.

Firestore tests will not run

Unit testing of the Firestore security rules should be done locally, because, as Firebase guru Todd Kerpelman explains in this video, they are faster, safer, and cheaper:

What is not explained, however, is how to deal with custom configurations.

If you follow the video with Firestore running on a non-default port, you encounter this error:

Could not reach Cloud Firestore backend. Connection failed 1 times. Most recent error: FirebaseError: [code=unavailable]: 14 UNAVAILABLE: No connection established

This typically indicates that your device does not have a healthy Internet connection at the moment. The client will operate in offline mode until it is able to successfully connect to the backend.

The solution

The explanation in the error message is not really helpful. In fact the problem is that you need to set up your tests to connect to your emulator:

describe("Our social app", () => {
    it("Can read items in the read-only collection", () => {
        const db = firebase.initializeTestApp({ projectId: MY_PROJECT_ID }).firestore()
        db.settings({ host: "localhost:65535", ssl: false });
        const testDoc = db.collection("readonly").doc("testDoc");
        qfirebase.assertSucceeds(testDoc.get());
    });
});

Cleaning the database when tests are finished

At the end of the tests, or even at the beginning of each test, you should clean the database. This can be done with

firebase.clearFirestoreData({projectId: MY_PROJECT_ID});

The problem is that this call tries to clear the data in the Firestore emulator on the default port, givin the error

connect ECONNREFUSED 127.0.0.1:8080

At this moment it is not possible to provide a port number to clearFirestoreData, so it is not possible to use that function to clear your local emulated Firestore.

The only solution is to manually remove the created data using the SDK:

after(async () => {
    const dbAdmin = firebase.initializeAdminApp({ projectId: MY_PROJECT_ID }).firestore();
    dbAdmin.settings({ host: "localhost:65535", ssl: false });
    await dbAdmin.collection("posts").doc("public_post").delete();
    await dbAdmin.collection("posts").doc("private_post").delete();
})

Just remember to delete document subcollections, if you created any, because they will not be deleted by deleting the parent document.

Subscribe to a curated newsletter

Receive an email every week with curated content about Dart and Flutter.

See previous issues of the newsletter.