How do I access Firebase Firestore from Dart VM / Dart integration tests?

Viewed 532

I am writing a Flutter app that has fairly complex logic over the Firebase Firestore documents. I am trying to write unit tests using flutter_test that actually execute this logic against the database (I know this is technically an integration test). This is because this logic has a lot of edge-cases I can only be sure are working if tested against the real database.

This seems to be an impossible task.

  1. The cloud_firestore package can only run inside the phone because of how the authentication was implemented.
  2. The firebase package has 2 "implementations". One can only work on the browser, and the other one, the Dart VM one, which is a low level wrapper around the REST API, is almost completely undocumented.

The 2. firebase package for Dart VM has this example:

import 'package:firebase/firebase_io.dart';

void main() {
  var credential = ... // Retrieve auth credential
  var fbClient = new FirebaseClient(credential); // FirebaseClient.anonymous() is also available

  var path = ... // Full path to your database location with .json appended

  // GET
  var response = await fbClient.get(path);

  // DELETE
  await fbClient.delete(path);

  ...
}

... however it does not show how to get the credential. The googleapis package shows how to get the credentials:

final _credentials = new ServiceAccountCredentials.fromJson(r'''
{
  "private_key_id": ...,
  "private_key": ...,
  "client_email": ...,
  "client_id": ...,
  "type": "service_account"
}
''');

... however this object is not a string and it is not written anywhere how to transform this into what the FirebaseClient class expects (toString() does not work). There is a Github issue on the firebase package on how to get this credentials but it is still unanswered.

I find it hard to believe that there is no information available online, that I could find, on how to write proper integration tests for Firebase Firestore.

Considerations:

  • I have no interest mocking Firestore because my logic is complex and I want to test each edge-case against the real database.
  • I have no interest using Flutter Driver because the tests are installed like normal apps in the phone and that takes time during development and it is not as straight-forward to debug as regular unit tests. Android Studio has very good test debug tools.

How should I write integration tests that access Firebase Firestore?

2 Answers

For others who were brought here, as I was a few hours ago, by a search for ways to create that mysterious credential object when using the dart VM with Firebase, the following is my solution which works for accessing Firestore, at least.

The work is done by the Credentials class: (implemented below)

import 'package:firebase/firebase_io.dart';

String credential = await Credentials.fetch();
FirebaseClient fbClient = new FirebaseClient(credential);

The Credentials class creates the credential, using data from the Firebase Service Account private key (Firebase Console > [Project] > Settings > Project Settings > Service Accounts, then download the private key).

import 'dart:io';
import 'dart:convert';
import "package:googleapis_auth/auth_io.dart";
import 'package:http/http.dart' as http;

class Credentials {

  /// Returns a credential string to be used in the constructor
  /// of [FirebaseClient].
  static Future<String> fetch() async {
    Map<String, dynamic> pk = await getPrivateKey();
    // Fields from Firebase private key
    var accountCredentials = ServiceAccountCredentials.fromJson({
      "private_key_id": pk['private_key_id'],
      "private_key": pk['private_key'],
      "client_email": pk['client_email'],
      "client_id": pk['client_id'],
      "type": "service_account",
    });

    // Define the required scopes.
    // see https://firebase.google.com/docs/firestore/use-rest-api#working_with_google_identity_oauth_20_tokens
    var scopes = [
      "https://www.googleapis.com/auth/datastore",
    ];

    var client = new http.Client();
    AccessCredentials credentials =
        await obtainAccessCredentialsViaServiceAccount(
            accountCredentials, scopes, client);
    client.close();
    return credentials.accessToken.data;
  }

  static Future<Map<String, dynamic>> getPrivateKey() async {
    String jsonString =
        await File('/Path/to/firebase_private_key/keyfile.json')
            .readAsString();
    return json.decode(jsonString);
  }
}

Adding the scope below allowed me to access firebase realtime

'https://www.googleapis.com/auth/userinfo.email'

According to https://developers.google.com/identity/protocols/oauth2/scopes it says that that scope is allowed to 'View your email address'. This doesn't seem quite right but it made it work.

(if anyone can help me understand this please add to the answer).

Related