How to use firestore emulator from client

Anthony JEAMME picture Anthony JEAMME · Aug 17, 2019 · Viewed 8.3k times · Source

I want to test locally my firebase functions. These functions make firestore queries.

So i start the emulator firebase emulators:start and in my client i use firebase.functions().useFunctionsEmulator('http://localhost:5001').

My functions work well when i call them in my client. I can read/write data inside the firestore emulator.

The problem :

I want to read the firestore emulator data directly inside my client, like :

firebase.firestore().collection('tests').get().then(tests => {
    console.log( tests.docs.map(test=>test.map) )
})

but i can't find how to set the firestore emulator inside my client.

here what i tried:

1) Firestore setting

firebase.firestore().settings({
    host:'http://localhost:8080',
    ssl:false
})

result :

i get @firebase/firestore: Firestore (6.3.5): Could not reach Cloud Firestore backend. Backend didn't respond within 10 seconds. inside my client console.

The http request returns 'Not found'

2) Set the emulator url inside my firebaseConfig

var firebaseConfig = {
    // ...
    databaseURL: "http://localhost:8080",
    // ...
}

firebase.initializeApp(firebaseConfig)

in this case, the remote server (https://firestore.googleapis.com..) is requested.

So i want to setup one of these two cases :

1) Using the remote firestore inside my functions emulators

OR

2) Using the local firestore emulator inside my client code.

Anyone has already done this ?

Answer

Clarity picture Clarity · Aug 17, 2019

Install the testing lib

npm i -D @firebase/testing

Setup and start the emulator in another terminal:

firebase setup:emulators:firestore

firebase serve --only firestore

Setup the tests

const firebase = require("@firebase/testing");

// Helper function to setup test db
function authedApp(auth) {
  return firebase
    .initializeTestApp({ projectId: FIRESTORE_PROJECT_ID, auth })
    .firestore();
}

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

// Clean up apps between tests.
afterEach(async () => {
  await Promise.all(firebase.apps().map(app => app.delete()));
});

Run the tests

it("should retrieve correct item", async () => {
  // Init test db
  const db = authedApp(null);

  // Manually add item to collection
  const ref = await db.collection(COLLECTION_NAME).add({name: 'test item'});

  // Fetch item by id 
  const resp = await db.collection(COLLECTION_NAME).doc(ref.id).get();

  // test the output
  expect(resp).toBeDefined();
  expect(resp).toEqual(expect.objectContaining({name: 'test item'}));
});

Of course your particular setup and circumstances will differ, but this at least should give you a general idea. More info: https://firebase.google.com/docs/rules/unit-tests

Note from 'Test your Cloud Firestore Security Rules'

Data written to the Cloud Firestore emulator is held in memory until the emulator is stopped. If the emulator is run continuously, this may have an impact on test isolation. To ensure that data written in one test is not read in another, either explicitly clear your data with clearFirestoreData, or 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.

Edit: I wrote a blog post a while back, which goes into more detail about the subject.