How to reset firestore emulator in integration test
Asked Answered
A

3

15

I'm running my integration-tests in flutter and dart using the firestore emulator. First I start the firestore emulator with some data like so: firebase emulators:start --import=./dir.

Then I start an android emulator and start the app I want to test on the android emulator. The app is configured to use the firestore emulator. Then I run a series of tests, which all write to the firestore emulator.

But on the beginning of each test, I want the data to be reset to the state, when I first started the emulator. So e.g. if the tests are executed in this order:

Test A Test B Test C

I don't want to have the data, Test A created to be present in the database, when Tests B and C are executed. I could terminate the firestore emulator and start it again at the beginning of each test. But this would make my tests a lot slower.

Do you know of a way to reset the data, which is present in the firebase emulator?

Acrylonitrile answered 18/12, 2020 at 10:31 Comment(3)
I don't think it's possible to come back to an initial state, what you could do is remove all the data and then reimport it, would that work? Not sure if this would be faster than running it again terminate and restart the emulator though.Utilitarian
I've the very same problem and I'm looking for a solution to automatically reset same parts of the emulator suite (RTDB, Firestore and Authentication). If you only want to have a clean Firestore maybe you can create a Firebase Function listening to an event (e.g. specific writing event in Firestore or deleting a user) and then deleting all the content of Firestore.Jolda
You can at least clear specific collections or documents in the firestore database by using the REST API. See here: firebase.google.com/docs/emulator-suite/… Also searching for a way to reset to the initially imported data.Santos
S
7

I am assuming you're referring to firestore when you say you want to 'reset the data'.

Per the documentation at https://firebase.google.com/docs/emulator-suite/install_and_configure#use_the_emulator_hub_rest_api

import fetch from 'node-fetch';

import firebaseConfig from '../../../firebase.json';
const hubEmulatorPort = firebaseConfig.emulators.hub.port;
const firestoreEmulatorPort = firebaseConfig.emulators.firestore.port;

async function clearDb() {
  const response = await fetch(
    `http://localhost:${firestoreEmulatorPort}/emulator/v1/projects/${process.env.PROJECT_ID}/databases/(default)/documents`,
    {
      method: 'DELETE',
    }
  );
  if (response.status !== 200) {
    throw new Error('Trouble clearing Emulator: ' + (await response.text()));
  }
}

async function populateDb(data) {
  // Logic for adding in any data you want in the db
  // before each test run
}

async function enableBackgroundTriggers() {
  const response = await fetch(`http://localhost:${hubEmulatorPort}/functions/enableBackgroundTriggers`, {
    method: 'PUT',
  });
  if (response.status !== 200) {
    throw new Error('Trouble enabling database triggers in emulator: ' + (await response.text()));
  }
}

async function disableBackgroundTriggers() {
  const response = await fetch(`http://localhost:${hubEmulatorPort}/functions/disableBackgroundTriggers`, {
    method: 'PUT',
  });
  if (response.status !== 200) {
    throw new Error('Trouble disabling database triggers in emulator: ' + (await response.text()));
  }
}

async function resetDb(data) {
  await disableBackgroundTriggers();
  await clearDb();
  await populateDb(data);
  await enableBackgroundTriggers();
}

export { resetDb };

I can't find a source for the clearing of the db, but the RESTful call in clearDb does what you want.

It's important to disable the triggers before clearing or populating the database, in case you have firestore triggers that modify data in ways your tests don't expect. I write tests by passing full DB state to the populateDb method, then reenable triggers before running tests so I can test said triggers. If you aren't running any firestore triggers, the clearDb call alone should be enough for your purposes.

My tests all have calls to resetDb() in my beforeEach hook in jest to ensure clean runs of each test. I recommend adding this to whatever 'beforeEach'-like hook your favorite testing API exposes.

If your tests do things like create users in Firebase Authentication you'll have to find another way to clear them between test runs.

If anyone can find documentation on how to clear other emulators in the Firebase Emulator Suite, please drop it in the comments. I am currently trying to find a way to clear Authentication emulators, which is actually how I found this question.

Best of luck!

Schnauzer answered 16/12, 2021 at 18:35 Comment(1)
The documentation for firebase is unfortunately quite scattered. But the refernece you are looking for is firebase.google.com/docs/emulator-suite/…Lauretta
I
0

Firebase provides a library called @firebase/rules-unit-testing, which provides many helper methods for unit testing.

Firestore

Resetting the Firestore Database could be done with <RulesTestEnvironment>.clearFirestore();. You can read more about it in their docs.

Using Jest you could use something like the following in your code to clear the database before each test:

beforeAll(async () => {
  testEnv = await initializeTestEnvironment({
    projectId: 'demo-test',
    firestore: { host: 'localhost', port: firestoreEmulatorPort },
  });
});

beforeEach(async () => {
  await testEnv.clearFirestore();
});

However, make sure that the Emulators are started in the background and that they are running on the specified firestoreEmulatorPort.

Here is an alternatate solution.

Authentication

If you want to reset the authentication, you can send a DELETE request to delete all users as mentioned in the docs here.

You could implement it in the testing file as follows:

async function clearAuth() {
  const res = await fetch(`http://localhost:${authEmulatorPort}/emulator/v1/projects/${firebaseProjectId}/accounts`, {
    method: 'DELETE',
    headers: {
      Authorization: 'Bearer owner',
    },
  });

  if (res.status !== 200) throw new Error('Unable to reset Authentication Emulators');
}

beforeEach(async () => {
  await clearAuth();
});
Inappreciative answered 29/3, 2023 at 10:6 Comment(0)
P
-1

If you want to clear out all the collections programatically, like in a setUp() or tearDown() there's a reference for that here: Delete data from Cloud Firestore - Delete Collections

Note that it's not recommended for all implementations, but there are examples in Java, Python, Node.js, go, PHP, C#, and Ruby.

Here's an example of how to iterate through all your collections and delete them all in Java, using the deleteCollection() method from that link.

public static void main(String[] args) throws IOException {
    final int BATCH_SIZE = 5;

    Firestore firestore = initializeCloudFirestore();

    for (CollectionReference listCollection : firestore.listCollections()) {
      deleteCollection(listCollection, BATCH_SIZE);
    }
  }

  /**
   * One way of initializing Firestore,
   * see other options at https://firebase.google.com/docs/firestore/quickstart#initialize
   */
  private static Firestore initializeCloudFirestore() throws IOException {
    // Use the application default credentials
    GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
    FirebaseOptions options = new FirebaseOptions.Builder()
        .setCredentials(credentials)
        .setProjectId("projectId")
        .build();
    FirebaseApp.initializeApp(options);

    Firestore firestore = FirestoreClient.getFirestore();
    return firestore;
  }

  /**
   * Delete a collection in batches to avoid out-of-memory errors. Batch size may be tuned based on
   * document size (atmost 1MB) and application requirements.
   * See https://firebase.google.com/docs/firestore/manage-data/delete-data#java_5
   */
  static void deleteCollection(CollectionReference collection, int batchSize) {
    try {
      // retrieve a small batch of documents to avoid out-of-memory errors
      ApiFuture<QuerySnapshot> future = collection.limit(batchSize).get();
      int deleted = 0;
      // future.get() blocks on document retrieval
      List<QueryDocumentSnapshot> documents = future.get().getDocuments();
      for (QueryDocumentSnapshot document : documents) {
        document.getReference().delete();
        ++deleted;
      }
      if (deleted >= batchSize) {
        // retrieve and delete another batch
        deleteCollection(collection, batchSize);
      }
    } catch (Exception e) {
      System.err.println("Error deleting collection : " + e.getMessage());
    }
  }

For the entire file, including imports, see this Github Gist.

Pingpingpong answered 24/1, 2023 at 20:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.