Catch up on highlights from Firebase at Google I/O 2023. Learn more

Unit-Tests von Cloud Functions

Auf dieser Seite werden Best Practices und Tools zum Schreiben von Komponententests für Ihre Funktionen beschrieben, z. B. Tests, die Teil eines Continuous Integration (CI)-Systems wären. Um das Testen zu vereinfachen, stellt Firebase das Firebase Test SDK for Cloud Functions bereit. Es wird auf npm als firebase-functions-test verteilt und ist ein begleitendes Test-SDK für firebase-functions . Das Firebase Test SDK für Cloud-Funktionen:

  • Kümmert sich um das entsprechende Setup und Teardown für Ihre Tests, z. B. das Setzen und Deaktivieren von Umgebungsvariablen, die von firebase-functions benötigt werden.
  • Generiert Beispieldaten und Ereigniskontext, sodass Sie nur die Felder angeben müssen, die für Ihren Test relevant sind.

Versuchsaufbau

Installieren Sie sowohl firebase-functions-test als auch Mocha , ein Testframework, indem Sie die folgenden Befehle in Ihrem Funktionsordner ausführen:

npm install --save-dev firebase-functions-test
npm install --save-dev mocha

Erstellen Sie als Nächstes einen test im Funktionsordner, erstellen Sie darin eine neue Datei für Ihren Testcode und nennen Sie sie etwa index.test.js .

Ändern Sie schließlich functions/package.json , um Folgendes hinzuzufügen:

"scripts": {
  "test": "mocha --reporter spec"
}

Nachdem Sie die Tests geschrieben haben, können Sie sie ausführen, indem Sie npm test in Ihrem Funktionsverzeichnis ausführen.

Initialisieren des Firebase Test SDK für Cloud-Funktionen

Es gibt zwei Möglichkeiten, firebase-functions-test zu verwenden:

  1. Onlinemodus (empfohlen): Schreiben Sie Tests, die mit einem Firebase-Projekt interagieren, das dem Testen gewidmet ist, sodass Datenbankschreibvorgänge, Benutzererstellungen usw. tatsächlich stattfinden und Ihr Testcode die Ergebnisse überprüfen kann. Das bedeutet auch, dass andere Google SDKs, die in Ihren Funktionen verwendet werden, ebenfalls funktionieren.
  2. Offline-Modus: Schreiben Sie Silo- und Offline-Einheitentests ohne Nebenwirkungen. Das bedeutet, dass alle Methodenaufrufe, die mit einem Firebase-Produkt interagieren (z. B. in die Datenbank schreiben oder einen Benutzer erstellen), gestubbt werden müssen. Die Verwendung des Offlinemodus wird im Allgemeinen nicht empfohlen, wenn Sie über Cloud Firestore- oder Realtime Database-Funktionen verfügen, da dies die Komplexität Ihres Testcodes erheblich erhöht.

SDK im Online-Modus initialisieren (empfohlen)

Wenn Sie Tests schreiben möchten, die mit einem Testprojekt interagieren, müssen Sie die Projektkonfigurationswerte angeben, die zum Initialisieren der App über firebase-admin erforderlich sind, sowie den Pfad zu einer Dienstkonto-Schlüsseldatei.

So rufen Sie die Konfigurationswerte Ihres Firebase-Projekts ab:

  1. Öffnen Sie Ihre Projekteinstellungen in der Firebase-Konsole .
  2. Wählen Sie in Ihre Apps die gewünschte App aus.
  3. Wählen Sie im rechten Bereich die Option zum Herunterladen einer Konfigurationsdatei für Apple- und Android-Apps aus.

    Wählen Sie für Web-Apps Config aus, um Konfigurationswerte anzuzeigen.

So erstellen Sie eine Schlüsseldatei:

  1. Öffnen Sie den Bereich Dienstkonten der Google Cloud Console.
  2. Wählen Sie das App Engine-Standarddienstkonto aus und verwenden Sie das Optionsmenü auf der rechten Seite, um Schlüssel erstellen auszuwählen.
  3. Wählen Sie bei Aufforderung JSON als Schlüsseltyp aus und klicken Sie auf Erstellen .

Initialisieren Sie nach dem Speichern der Schlüsseldatei das SDK:

// At the top of test/index.test.js
const test = require('firebase-functions-test')({
  databaseURL: 'https://my-project.firebaseio.com',
  storageBucket: 'my-project.appspot.com',
  projectId: 'my-project',
}, 'path/to/serviceAccountKey.json');

SDK im Offline-Modus initialisieren

Wenn Sie vollständige Offline-Tests schreiben möchten, können Sie das SDK ohne Parameter initialisieren:

// At the top of test/index.test.js
const test = require('firebase-functions-test')();

Verspotten von Konfigurationswerten

Wenn Sie functions.config() in Ihrem Funktionscode verwenden, können Sie die Konfigurationswerte simulieren. Wenn beispielsweise functions/index.js den folgenden Code enthält:

const functions = require('firebase-functions');
const key = functions.config().stripe.key;

Dann können Sie den Wert in Ihrer Testdatei wie folgt nachahmen:

// Mock functions config values
test.mockConfig({ stripe: { key: '23wr42ewr34' }});

Importieren Ihrer Funktionen

Um Ihre Funktionen zu importieren, verwenden Sie require , um Ihre Hauptfunktionsdatei als Modul zu importieren. Stellen Sie sicher, dass Sie dies nur tun, nachdem Sie firebase-functions-test initialisiert und Konfigurationswerte verspottet haben.

// after firebase-functions-test has been initialized
const myFunctions = require('../index.js'); // relative path to functions code

Wenn Sie firebase-functions-test im Offline-Modus initialisiert haben und Sie admin.initializeApp() in Ihrem Funktionscode haben, müssen Sie es vor dem Importieren Ihrer Funktionen stubn:

// If index.js calls admin.initializeApp at the top of the file,
// we need to stub it out before requiring index.js. This is because the
// functions will be executed as a part of the require process.
// Here we stub admin.initializeApp to be a dummy function that doesn't do anything.
adminInitStub = sinon.stub(admin, 'initializeApp');
// Now we can require index.js and save the exports inside a namespace called myFunctions.
myFunctions = require('../index');

Testen von Hintergrundfunktionen (nicht-HTTP).

Der Prozess zum Testen von Nicht-HTTP-Funktionen umfasst die folgenden Schritte:

  1. Umschließen Sie die Funktion, die Sie testen möchten, mit der Methode test.wrap
  2. Testdaten erstellen
  3. Rufen Sie die umschlossene Funktion mit den von Ihnen erstellten Testdaten und allen Ereigniskontextfeldern auf, die Sie angeben möchten.
  4. Behauptungen über das Verhalten aufstellen.

Umschließen Sie zuerst die Funktion, die Sie testen möchten. Angenommen, Sie haben in functions/index.js eine Funktion mit dem Namen makeUppercase , die Sie testen möchten. Schreiben Sie Folgendes in functions/test/index.test.js

// "Wrap" the makeUpperCase function from index.js
const myFunctions = require('../index.js');
const wrapped = test.wrap(myFunctions.makeUppercase);

wrapped ist eine Funktion, die makeUppercase aufruft, wenn sie aufgerufen wird. wrapped nimmt 2 Parameter:

  1. data (erforderlich): die an makeUppercase zu sendenden Daten. Dies entspricht direkt dem ersten Parameter, der an den von Ihnen geschriebenen Funktionshandler gesendet wird. firebase-functions-test stellt Methoden zum Erstellen benutzerdefinierter Daten oder Beispieldaten bereit.
  2. eventContextOptions (optional): Felder des Ereigniskontexts, den Sie angeben möchten. Der Ereigniskontext ist der zweite Parameter, der an den von Ihnen geschriebenen Funktionshandler gesendet wird. Wenn Sie beim Aufrufen wrapped keinen eventContextOptions -Parameter einschließen, wird dennoch ein Ereigniskontext mit sinnvollen Feldern generiert. Sie können einige der generierten Felder überschreiben, indem Sie sie hier angeben. Beachten Sie, dass Sie nur die Felder einschließen müssen, die Sie überschreiben möchten. Alle Felder, die Sie nicht überschrieben haben, werden generiert.
const data = … // See next section for constructing test data

// Invoke the wrapped function without specifying the event context.
wrapped(data);

// Invoke the function, and specify params
wrapped(data, {
  params: {
    pushId: '234234'
  }
});

// Invoke the function, and specify auth and auth Type (for real time database functions only)
wrapped(data, {
  auth: {
    uid: 'jckS2Q0'
  },
  authType: 'USER'
});

// Invoke the function, and specify all the fields that can be specified
wrapped(data, {
  eventId: 'abc',
  timestamp: '2018-03-23T17:27:17.099Z',
  params: {
    pushId: '234234'
  },
  auth: {
    uid: 'jckS2Q0' // only for real time database functions
  },
  authType: 'USER' // only for real time database functions
});

Aufbau von Testdaten

Der erste Parameter einer umschlossenen Funktion sind die Testdaten, mit denen die zugrunde liegende Funktion aufgerufen wird. Es gibt eine Reihe von Möglichkeiten, Testdaten zu erstellen.

Verwenden von benutzerdefinierten Daten

firebase-functions-test verfügt über eine Reihe von Funktionen zum Erstellen von Daten, die zum Testen Ihrer Funktionen erforderlich sind. Verwenden Sie beispielsweise test.firestore.makeDocumentSnapshot , um einen Firestore DocumentSnapshot zu erstellen. Das erste Argument sind die Daten und das zweite Argument ist der vollständige Referenzpfad, und es gibt ein optionales drittes Argument für andere Eigenschaften des Snapshots, das Sie angeben können.

// Make snapshot
const snap = test.firestore.makeDocumentSnapshot({foo: 'bar'}, 'document/path');
// Call wrapped function with the snapshot
const wrapped = test.wrap(myFunctions.myFirestoreDeleteFunction);
wrapped(snap);

Wenn Sie eine onUpdate oder onWrite Funktion testen, müssen Sie zwei Snapshots erstellen: einen für den Vorher-Zustand und einen für den Nachher-Zustand. Anschließend können Sie mit der makeChange Methode ein Change Objekt mit diesen Snapshots erstellen.

// Make snapshot for state of database beforehand
const beforeSnap = test.firestore.makeDocumentSnapshot({foo: 'bar'}, 'document/path');
// Make snapshot for state of database after the change
const afterSnap = test.firestore.makeDocumentSnapshot({foo: 'faz'}, 'document/path');
const change = test.makeChange(beforeSnap, afterSnap);
// Call wrapped function with the Change object
const wrapped = test.wrap(myFunctions.myFirestoreUpdateFunction);
wrapped(change);

Siehe die API-Referenz für ähnliche Funktionen für alle anderen Datentypen.

Verwendung von Beispieldaten

Wenn Sie die in Ihren Tests verwendeten Daten nicht anpassen müssen, bietet firebase-functions-test Methoden zum Generieren von Beispieldaten für jeden Funktionstyp.

// For Firestore onCreate or onDelete functions
const snap = test.firestore.exampleDocumentSnapshot();
// For Firestore onUpdate or onWrite functions
const change = test.firestore.exampleDocumentSnapshotChange();

In der API-Referenz finden Sie Methoden zum Abrufen von Beispieldaten für jeden Funktionstyp.

Stubdaten verwenden (für den Offline-Modus)

Wenn Sie das SDK im Offline-Modus initialisiert haben und eine Cloud Firestore- oder Realtime Database-Funktion testen, sollten Sie ein einfaches Objekt mit Stubs verwenden, anstatt einen tatsächlichen DocumentSnapshot oder DataSnapshot zu erstellen.

Angenommen, Sie schreiben einen Komponententest für die folgende Funktion:

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return snapshot.ref.parent.child('uppercase').set(uppercase);
    });

Innerhalb der Funktion wird snap zweimal verwendet:

  • snap.val()
  • snap.ref.parent.child('uppercase').set(uppercase)

Erstellen Sie im Testcode ein einfaches Objekt, in dem beide Codepfade funktionieren, und verwenden Sie Sinon , um die Methoden zu stubben.

// The following lines creates a fake snapshot, 'snap', which returns 'input' when snap.val() is called,
// and returns true when snap.ref.parent.child('uppercase').set('INPUT') is called.
const snap = {
  val: () => 'input',
  ref: {
    parent: {
      child: childStub,
    }
  }
};
childStub.withArgs(childParam).returns({ set: setStub });
setStub.withArgs(setParam).returns(true);

Behauptungen aufstellen

Nachdem Sie das SDK initialisiert, die Funktionen verpackt und Daten erstellt haben, können Sie die verpackten Funktionen mit den erstellten Daten aufrufen und Aussagen zum Verhalten machen. Sie können eine Bibliothek wie Chai verwenden, um diese Behauptungen aufzustellen.

Behauptungen im Online-Modus aufstellen

Wenn Sie das Firebase Test SDK for Cloud Functions im Onlinemodus initialisiert haben, können Sie mithilfe des firebase-admin SDK bestätigen, dass die gewünschten Aktionen (z. B. ein Datenbankschreibvorgang) stattgefunden haben.

Das folgende Beispiel bestätigt, dass „INPUT“ in die Datenbank des Testprojekts geschrieben wurde.

// Create a DataSnapshot with the value 'input' and the reference path 'messages/11111/original'.
const snap = test.database.makeDataSnapshot('input', 'messages/11111/original');

// Wrap the makeUppercase function
const wrapped = test.wrap(myFunctions.makeUppercase);
// Call the wrapped function with the snapshot you constructed.
return wrapped(snap).then(() => {
  // Read the value of the data at messages/11111/uppercase. Because `admin.initializeApp()` is
  // called in functions/index.js, there's already a Firebase app initialized. Otherwise, add
  // `admin.initializeApp()` before this line.
  return admin.database().ref('messages/11111/uppercase').once('value').then((createdSnap) => {
    // Assert that the value is the uppercased version of our input.
    assert.equal(createdSnap.val(), 'INPUT');
  });
});

Behauptungen im Offline-Modus aufstellen

Sie können Aussagen über den erwarteten Rückgabewert der Funktion machen:

const childParam = 'uppercase';
const setParam = 'INPUT';
// Stubs are objects that fake and/or record function calls.
// These are excellent for verifying that functions have been called and to validate the
// parameters passed to those functions.
const childStub = sinon.stub();
const setStub = sinon.stub();
// The following lines creates a fake snapshot, 'snap', which returns 'input' when snap.val() is called,
// and returns true when snap.ref.parent.child('uppercase').set('INPUT') is called.
const snap = {
  val: () => 'input',
  ref: {
    parent: {
      child: childStub,
    }
  }
};
childStub.withArgs(childParam).returns({ set: setStub });
setStub.withArgs(setParam).returns(true);
// Wrap the makeUppercase function.
const wrapped = test.wrap(myFunctions.makeUppercase);
// Since we've stubbed snap.ref.parent.child(childParam).set(setParam) to return true if it was
// called with the parameters we expect, we assert that it indeed returned true.
return assert.equal(wrapped(snap), true);

Sie können auch Sinon-Spione verwenden, um zu bestätigen, dass bestimmte Methoden aufgerufen wurden, und zwar mit Parametern, die Sie erwarten.

Testen von HTTP-Funktionen

Gehen Sie zum Testen von HTTP-onCall-Funktionen genauso vor wie beim Testen von Hintergrundfunktionen .

Wenn Sie HTTP-onRequest-Funktionen testen, sollten Sie firebase-functions-test verwenden, wenn:

  • Sie verwenden functions.config()
  • Ihre Funktion interagiert mit einem Firebase-Projekt oder anderen Google-APIs und Sie möchten ein echtes Firebase-Projekt und seine Anmeldedaten für Ihre Tests verwenden.

Eine HTTP-onRequest-Funktion benötigt zwei Parameter: ein Anforderungsobjekt und ein Antwortobjekt. So könnten Sie die Beispielfunktion addMessage() testen:

  • Überschreiben Sie die Umleitungsfunktion im Antwortobjekt, da sendMessage() sie aufruft.
  • Verwenden Sie innerhalb der Umleitungsfunktion chai.assert , um Aussagen darüber zu treffen, mit welchen Parametern die Umleitungsfunktion aufgerufen werden sollte:
// A fake request object, with req.query.text set to 'input'
const req = { query: {text: 'input'} };
// A fake response object, with a stubbed redirect function which asserts that it is called
// with parameters 303, 'new_ref'.
const res = {
  redirect: (code, url) => {
    assert.equal(code, 303);
    assert.equal(url, 'new_ref');
    done();
  }
};

// Invoke addMessage with our fake request and response objects. This will cause the
// assertions in the response object to be evaluated.
myFunctions.addMessage(req, res);

Bereinigung testen

Rufen Sie ganz am Ende Ihres Testcodes die Bereinigungsfunktion auf. Dadurch werden die Umgebungsvariablen zurückgesetzt, die das SDK bei der Initialisierung festgelegt hat, und Firebase-Apps gelöscht, die möglicherweise erstellt wurden, wenn Sie das SDK zum Erstellen einer Echtzeitdatenbank DataSnapshot oder Firestore DocumentSnapshot verwendet haben.

test.cleanup();

Sehen Sie sich vollständige Beispiele an und erfahren Sie mehr

Sie können die vollständigen Beispiele im Firebase GitHub-Repository überprüfen.

Weitere Informationen finden Sie in der API-Referenz für firebase-functions-test .