Run Java tests against the Firestore emulator

It's not widely documented how to do this, right? You have a test suite that you want to run against Firestore emulator, which should get started and stopped automatically while the tests are running. In this case, we will be using Java.

Connecting to local Firestore is quite straightforward, though poorly documented:

var options = FirestoreOptions.getDefaultInstance()
  .toBuilder()
  .setProjectId("my_project_id");
var firestoreUrl = System.getenv("FIRESTORE_URL");
if (!Strings.isNullOrEmpty(firestoreUrl) {
  options.setHost(firestoreUrl);
  options.setCredentialsProvider(null);
} else {
  options.setCredentials(GoogleCredentials.getApplicationDefault());
}
Firestore instance = options.build().getService();

This will connect Firestore to your local environment if you pass an environment variable named FIRESTORE_URL, and if not defined it will try to connect to the real service using the default Service Account for Compute Engine (if the code is running on a CI/CD environment, or just fail noisily if it's your local laptop). You can configure gradle to launch the emulator before your tests.

bin/start-firestore-emulator will launch the emulator on port 9000:

#!/bin/bash
# Launch firestore emulator on port 9000

waitport() {
  while ! nc -z localhost $1; do 
    sleep 1 
  done
}

PID=$(lsof -t -i :9000 -s tcp:LISTEN)
if [ -z "$PID" ]; then
  echo "Starting mock Firestore server on port 9000"
  nohup gcloud beta emulators firestore start \
    --host-port=127.0.0.1:9000 \
    > /tmp/mock-firestore-logs &
  waitport 9000
else
  echo "There is an instance of Firestore already running on port 9000"
fi

bin/stop-firestore-emulator will kill whatever is running on port 9000, so better it be your emulator:

#!/bin/bash
# Stop mock firestore 

PID=$(lsof -t -i :9000 -s tcp:LISTEN)
if [ ! -z "$PID" ]; then
  echo "Stopping mock Firestore server"
  kill "$PID"
fi

These two scripts will start and stop Firestore locally on port 9000. Now we need to trigger them before and after launching our tests in build.gradle:

task startMockFirestore {
  doFirst {
    ext.process = new ProcessBuilder()
      .directory(projectDir)
      .inheritIO()
      .command("bin/start-firestore-emulator")
      .start()
      .waitFor()
    }
}
tasks.test.dependsOn(startMockFirestore)

task stopMockFirestore(type: Exec) {
  executable "bin/stop-firestore-emulator"
}
test.finalizedBy(stopMockFirestore)

As bonus points, IntelliJ will automatically use these tasks when launching individual tests, which is nice. One last note: remember that the Firestore emulator will not persist anything between executions – once you kill the server, you lose the data.

Hope this is useful!