Catch up on everthing we announced at this year's Firebase Summit. 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 comme firebase-functions-test , et est un SDK de test compagnon pour firebase-functions . Le SDK Firebase Test pour Cloud Functions :

  • Prend en charge la configuration appropriée et désassemblage pour vos tests, tels que l' établissement et les variables d' environnement nécessaires MHS 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

Installer les deux firebase-functions-test et Mocha , un cadre de test, en exécutant les commandes suivantes dans vos fonctions dossier:

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

Créez ensuite un test de dossier dans le dossier fonctions, créer un nouveau fichier à l' intérieur de votre code de test, et nommez quelque chose comme index.test.js .

Enfin, modifier les functions/package.json ajouter ce qui suit:

"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 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é): essais Ecrire qu'Interact avec un projet Firebase dédié à tester de telle sorte que la base de données écrit, utilisateur crée, etc. serait effectivement se produire, et votre code de test peut vérifier les résultats. Cela signifie également que les autres SDK Google utilisés dans vos fonctions fonctionneront également.
  2. Mode hors ligne: Ecrire cloisonnée et tests unitaires 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 des tests d'écriture qui interagissent avec un projet de test, vous devez fournir les valeurs de configuration du projet qui sont nécessaires pour l' initialisation de l'application par firebase-admin , et le chemin vers un fichier clé de compte de service.

Pour obtenir les valeurs de configuration de votre projet Firebase :

  1. Ouvrez vos paramètres du 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 Apple 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 des comptes de service de la Google Cloud Console.
  2. Sélectionnez le App Engine compte de service par défaut, et utiliser le menu des options à droite pour sélectionner Créer clé.
  3. Lorsque vous ê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 se moquer des valeurs de configuration. Par exemple, si des 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, l' utilisation require d'importer votre fichier principal de fonctions en tant que module. Assurez - vous de ne le faire après l' initialisation firebase-functions-test , et se moquant des 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 vous avez admin.initializeApp() dans votre code de fonction, vous devez le stub 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 la test.wrap méthode
  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. Disons que vous avez une fonction dans les functions/index.js appelé makeUppercase , que vous souhaitez tester. ÉCRIRE dans les 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. données (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 de données personnalisées ou par exemple.
  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 des données nécessaires pour tester vos fonctions. Par exemple, utiliser test.firestore.makeDocumentSnapshot pour créer un Firestore DocumentSnapshot . Le premier argument est les données, et le second argument est le chemin de référence complet, et il y a un troisième argument optionnel pour d' 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 un onUpdate ou onWrite fonction, vous devrez créer deux instantanés: un pour l'avant l' état et l' autre pour l'après état. Ensuite, vous pouvez utiliser la makeChange méthode pour créer un Change objet 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);

Voir 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 les tests, puis vos firebase-functions-test propose des méthodes pour générer des données d'exemple 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();

Voir la référence de l' API pour les méthodes pour obtenir des données par exemple 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 ligne, et testez un nuage Firestore ou en temps réel la fonction de base de données, vous devez utiliser un objet simple avec des talons au lieu de créer une réelle 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);
    });

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

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

Dans le code de test, créez un objet plaine où ces deux chemins de code fonctionnera, et utiliser Sinon pour bouchonner 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 tels que Chai pour faire ces affirmations.

Faire des affirmations en mode en ligne

Si vous avez initialisé le SDK de test Firebase pour les fonctions Cloud en mode en firebase-admin ligne , vous pouvez affirmer que les actions souhaitées (comme une écriture de base de données) a eu lieu en utilisant le firebase-admin SDK.

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 des espions SINON Affirmer que certaines méthodes ont été appelés, et avec des paramètres que vous attendez.

Tester les fonctions HTTP

Pour tester les fonctions HTTP ONCall, utiliser la même approche que le test de fonctions de base .

Si vous testez les 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 vous pouvez tester la addMessage() fonction exemple :

  • Remplacer la fonction de redirection dans l'objet de réponse, étant donné que sendMessage() appelle.
  • Dans la fonction de redirection, utilisez chai.assert pour aider à faire des affirmations sur les paramètres de la fonction de redirection doit être appelée avec:
// 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);

Tester le nettoyage

À la toute fin de votre code de test, appelez la fonction de nettoyage. Cette variable d'environnement de Rétablit que l'ensemble du SDK quand il a été initialisé, et supprime les applications Firebase qui peuvent avoir été créés si vous avez utilisé le kit de développement 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 de l' firebase-functions-test API pour les firebase-functions-test .