Our Blog
blog header image
A month of Flutter: set up Firestore rules tests
Abraham Williams
December 26, 2018

Category

Development

One aspect of using Firestore for my data backend means I need to be certain my security rules are configured correctly. Otherwise users might be able to read or write date they shouldn't have access to.

A few days ago I set up Firestore in the server directory. I'm going to continue that work and configure tests to run on the Firestore emulator based off of the typescript-qickstart example.

In package.json I'll add some devDependencies and define several scripts. Node package scripts can be run with npm run <name>.

  • postinstall will set up the Firestore emulator after npm install is run in the scripts directory
  • start-emulator will will do just that
  • pretest will compile the project's TypeScript files before the test script is run
  • test will run the actual tests within the test directory using mocha test runner
  • posttest will cleanup the test *.js and *.js.map files created during pretest
  • ci uses a handy Node package start-server-and-test to start the emulator, wait for it to be ready, run the tests, and then shut down

I created a new tsconfig.json file with npx tsc --init. The two main changes I made were to target es6 instead of es5 and enable experimentalDecorators for the mocha-typescript package.

Within test/firestore.ts I'm defining a FirestoreTest class that will handle loading the rules, and setting up and tearing down test databases. mocha-typescript will use a new instance of this class for each test. Each instance will use a different projectId to avoid different test runs from interfering with each other.

The Cloud Firestore emulator persists data. This might impact your results. To run tests independently, assign a different project ID for each, independent test. When you call firebase.initializeAdminApp or firebase.initializeTestApp, append a user ID, timestamp, or random integer to the projectID.

Test your security rules

I changed firestore.rules so there was an allowed rule and a denied rule. These will be updated with real rules before the next deploy.

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if true;
      allow write: if false;
    }
  }
}

The initial tests for the user collection in user_rules_test.ts look like this:

@suite
class Users extends FirestoreTest {
  @test
  async 'can read'() {
    const user = this.db().collection('users').doc('alice');
    await firebase.assertSucceeds(user.get());
  }

  @test
  async 'can not write'() {
    const user = this.db().collection('users').doc('alice');
    await firebase.assertFails(user.set({ nickname: 'alice' }));
  }
}

The @suite, @test, and class style is supported by mocha-typescript. One of the reasons I chose TypeScript instead of JavaScript is because the types are similar to Dart much of the time.

I created a success test and a failure test to as proof of concept while setting up all the tooling. They get a database handle and assert that it can be read or written to.

I can now run npm run ci and see the following:

  Users
    ✓ can read (162ms)
    ✓ can not write (95ms)
  2 passing (462ms)

The next step will be to get these tests running along with the existing CI.

Code changes