Check out what’s new from Firebase@ Google I/O 2021, and join our alpha program for early access to the new Remote Config personalization feature. Learn more

Tests unitaires de Cloud Functions

Cette page décrit les meilleures pratiques et les outils pour écrire des tests unitaires pour vos fonctions, tels que des tests qui feraient partie d'un système d'intégration continue (CI). Pour faciliter les tests, Firebase fournit le SDK Firebase Test pour Cloud Functions. Il est distribué sur npm en tant que firebase-functions-test , et est un SDK de test compagnon de firebase-functions . Le SDK Firebase Test pour Cloud Functions :

  • Prend en charge la configuration et le démontage appropriés pour vos tests, tels que la définition et la désactivation des variables d'environnement nécessaires à firebase-functions .
  • Génère des exemples de données et de contexte d'événement, de sorte que vous n'avez qu'à spécifier les champs pertinents pour votre test.

Tester la configuration

Installez à la fois firebase-functions-test et Mocha , un framework de test, en exécutant les commandes suivantes dans votre dossier functions :

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

Ensuite, créez un dossier de test dans le dossier functions, créez un nouveau fichier à l'intérieur pour votre code de test et nommez-le quelque chose comme index.test.js .

Enfin, modifiez functions/package.json pour ajouter les éléments suivants :

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

Une fois que vous avez écrit les tests, vous pouvez les exécuter en exécutant npm test dans votre répertoire de fonctions.

Initialisation du SDK Firebase Test pour Cloud Functions

Il y a deux façons d'utiliser firebase-functions-test :

  1. Mode en ligne (recommandé) : écrivez des tests qui interagissent avec un projet Firebase dédié aux tests afin que les écritures de base de données, les créations par l'utilisateur, etc. se produisent réellement et que votre code de test puisse inspecter les résultats. Cela signifie également que les autres SDK Google utilisés dans vos fonctions fonctionneront également.
  2. Mode hors ligne : écrivez des tests unitaires en silos et hors ligne sans effets secondaires. Cela signifie que tous les appels de méthode qui interagissent avec un produit Firebase (par exemple, écrire dans la base de données ou créer un utilisateur) doivent être remplacés. L'utilisation du mode hors ligne n'est généralement pas recommandée si vous disposez de fonctions Cloud Firestore ou Realtime Database, car cela augmente considérablement la complexité de votre code de test.

Initialiser le SDK en mode en ligne (recommandé)

Si vous souhaitez écrire des tests qui interagissent avec un projet de test, vous devez fournir les valeurs de configuration du projet nécessaires à l'initialisation de l'application via firebase-admin , ainsi que le chemin d'accès à un fichier de clé de compte de service.

Pour obtenir les valeurs de configuration de votre projet Firebase :

  1. Ouvrez les paramètres de votre projet dans la console Firebase .
  2. Dans Vos applications, sélectionnez l'application souhaitée.
  3. Dans le volet de droite, sélectionnez l'option pour télécharger un fichier de configuration pour les applications iOS et Android.

    Pour les applications Web, sélectionnez Config pour afficher les valeurs de configuration.

Pour créer un fichier clé :

  1. Ouvrez le volet Comptes de service de Google Cloud Console.
  2. Sélectionnez le compte de service par défaut App Engine et utilisez le menu d'options à droite pour sélectionner Créer une clé .
  3. Lorsque vous y êtes invité, sélectionnez JSON pour le type de clé, puis cliquez sur Créer .

Après avoir enregistré le fichier de clé, initialisez le 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');

Initialiser le SDK en mode hors ligne

Si vous souhaitez écrire des tests complètement hors ligne, vous pouvez initialiser le SDK sans aucun paramètre :

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

Valeurs de configuration moqueuses

Si vous utilisez functions.config() dans votre code de fonctions, vous pouvez vous moquer des valeurs de configuration. Par exemple, si functions/index.js contient le code suivant :

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

Ensuite, vous pouvez vous moquer de la valeur dans votre fichier de test comme suit :

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

Importer vos fonctions

Pour importer vos fonctions, utilisez require pour importer votre fichier de fonctions principales en tant que module. Assurez-vous de ne le faire qu'après avoir initialisé firebase-functions-test et moqué les valeurs de configuration.

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

Si vous avez initialisé firebase-functions-test en mode hors ligne et que vous avez admin.initializeApp() dans votre code de fonctions, vous devez le remplacer avant d'importer vos fonctions :

// 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');

Test des fonctions d'arrière-plan (non HTTP)

Le processus de test des fonctions non HTTP comprend les étapes suivantes :

  1. Enveloppez la fonction que vous souhaitez tester avec la méthode test.wrap
  2. Construire des données de test
  3. Appelez la fonction encapsulée avec les données de test que vous avez construites et tous les champs de contexte d'événement que vous souhaitez spécifier.
  4. Faire des affirmations sur le comportement.

Enveloppez d'abord la fonction que vous souhaitez tester. Supposons que vous ayez une fonction dans functions/index.js appelée makeUppercase , que vous voudriez tester. Écrivez ce qui suit dans functions/test/index.test.js

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

wrapped est une fonction qui appelle makeUppercase lorsqu'elle est appelée. wrapped prend 2 paramètres :

  1. data (obligatoire) : les données à envoyer à makeUppercase . Cela correspond directement au premier paramètre envoyé au gestionnaire de fonction que vous avez écrit. firebase-functions-test fournit des méthodes pour construire des données personnalisées ou des exemples de données.
  2. eventContextOptions (facultatif) : champs du contexte de l'événement que vous souhaitez spécifier. Le contexte d'événement est le deuxième paramètre envoyé au gestionnaire de fonction que vous avez écrit. Si vous ne pas inclure un eventContextOptions paramètre lors de l' appel wrapped , un contexte d'événement est toujours généré avec des champs sensibles. Vous pouvez remplacer certains des champs générés en les spécifiant ici. Notez que vous n'avez qu'à inclure les champs que vous souhaitez remplacer. Tous les champs que vous n'avez pas remplacés sont générés.
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
});

Construction des données de test

Le premier paramètre d'une fonction encapsulée est la donnée de test avec laquelle appeler la fonction sous-jacente. Il existe plusieurs manières de construire des données de test.

Utilisation de données personnalisées

firebase-functions-test a un certain nombre de fonctions pour construire les données nécessaires pour tester vos fonctions. Par exemple, utilisez test.firestore.makeDocumentSnapshot pour créer un Firestore DocumentSnapshot . Le premier argument est les données, et le deuxième argument est le chemin de référence complet, et il existe un troisième argument facultatif pour les autres propriétés de l'instantané que vous pouvez spécifier.

// 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);

Si vous testez une fonction onUpdate ou onWrite , vous devrez créer deux instantanés : un pour l'état avant et un pour l'état après. Ensuite, vous pouvez utiliser la méthode makeChange pour créer un objet Change avec ces instantanés.

// 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);

Consultez la référence de l' API pour des fonctions similaires pour tous les autres types de données.

Utiliser des exemples de données

Si vous n'avez pas besoin de personnaliser les données utilisées dans vos tests, alors firebase-functions-test propose des méthodes pour générer des exemples de données pour chaque type de fonction.

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

Consultez la référence API pour les méthodes permettant d'obtenir des exemples de données pour chaque type de fonction.

Utilisation des données de remplacement (pour le mode hors ligne)

Si vous avez initialisé le SDK en mode hors connexion et que vous testez une fonction Cloud Firestore ou Realtime Database, vous devez utiliser un objet simple avec des stubs au lieu de créer un véritable DocumentSnapshot ou DataSnapshot .

Disons que vous écrivez un test unitaire pour la fonction suivante :

// 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);
    });

À l'intérieur de la fonction, snap est utilisé deux fois :

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

Dans le code de test, créez un objet simple où ces deux chemins de code fonctionneront et utilisez Sinon pour remplacer les méthodes.

// 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);

Faire des affirmations

Après avoir initialisé le SDK, encapsulé les fonctions et construit les données, vous pouvez appeler les fonctions encapsulées avec les données construites et faire des assertions sur le comportement. Vous pouvez utiliser une bibliothèque telle que Chai pour faire ces assertions.

Faire des affirmations en mode en ligne

Si vous avez initialisé le SDK Firebase Test pour Cloud Functions en mode en ligne , vous pouvez affirmer que les actions souhaitées (telles qu'une écriture de base de données) ont eu lieu à l'aide du SDK firebase-admin .

L'exemple ci-dessous affirme que 'INPUT' a été écrit dans la base de données du projet de test.

// 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');
  });
});

Faire des assertions en mode hors ligne

Vous pouvez faire des assertions sur la valeur de retour attendue de la fonction :

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);

Vous pouvez également utiliser les espions Sinon pour affirmer que certaines méthodes ont été appelées, et avec les paramètres que vous attendez.

Tester les fonctions HTTP

Pour tester les fonctions HTTP onCall, utilisez la même approche que le test des fonctions d'arrière-plan .

Si vous testez des fonctions HTTP onRequest, vous devez utiliser firebase-functions-test si :

  • Vous utilisez functions.config()
  • Votre fonction interagit avec un projet Firebase ou d'autres API Google, et vous souhaitez utiliser un véritable projet Firebase et ses identifiants pour vos tests.

Une fonction HTTP onRequest prend deux paramètres : un objet de requête et un objet de réponse. Voici comment tester l' exemple de fonction addMessage() :

  • Remplacez la fonction de redirection dans l'objet de réponse, puisque sendMessage() appelle.
  • Dans la fonction de redirection, utilisez chai.assert pour aider à faire des assertions sur les paramètres avec lesquels la fonction de redirection doit être appelée :
0fév06a220

Tester le nettoyage

À la toute fin de votre code de test, appelez la fonction de nettoyage. Cela annule les variables d'environnement que le SDK a définies lors de son initialisation et supprime les applications Firebase qui peuvent avoir été créées si vous avez utilisé le SDK pour créer une base de données en temps réel DataSnapshot ou Firestore DocumentSnapshot .

test.cleanup();

Passez en revue des exemples complets et en savoir plus

Vous pouvez consulter les exemples complets sur le référentiel Firebase GitHub.

Pour en savoir plus, reportez-vous à la référence API pour firebase-functions-test .