Cette page décrit les bonnes pratiques et les outils à utiliser pour écrire des tests unitaires pour vos fonctions, tels que les tests qui feraient partie d'un système d'intégration continue (CI). Pour faciliter les tests, Firebase fournit la Firebase Test SDK pour Cloud Functions. Il est distribué sur npm en tant que firebase-functions-test
et constitue un SDK de test associé à firebase-functions
. Le Firebase Test SDK pour Cloud Functions:
- Il gère la configuration et la suppression appropriées pour vos tests, par exemple en définissant et en désactivant les variables d'environnement requises par
firebase-functions
. - Génère des exemples de données et de contexte d'événement, de sorte que vous n'ayez qu'à spécifier les champs pertinents pour votre test.
Configuration du test
Installez firebase-functions-test
et Mocha, un framework de test, en exécutant les commandes suivantes dans votre dossier de fonctions :
npm install --save-dev firebase-functions-test
npm install --save-dev mocha
Créez ensuite un dossier test
dans le dossier "functions", créez-y un fichier pour votre code de test et nommez-le 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 de Firebase Test SDK pour Cloud Functions...
Vous pouvez utiliser firebase-functions-test
de deux façons :
- 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 d'utilisateurs, 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.
- Mode hors connexion:écrivez des tests unitaires cloisonnés et hors connexion sans effets secondaires. Cela signifie que tous les appels de méthode qui interagissent avec un produit Firebase (par exemple, l'écriture dans la base de données ou la création d'un utilisateur) doivent être simulés. L'utilisation du mode hors connexion n'est généralement pas recommandée si vous disposez de fonctions Cloud Firestore ou Realtime Database, car elle 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 pour initialiser 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:
- Ouvrez les paramètres de votre projet dans la console Firebase.
- Dans Vos applications,sélectionnez l'application de votre choix.
Dans le volet de droite, sélectionnez l'option permettant de 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 de clé:
- Ouvrez le volet des comptes de service de la console Google Cloud.
- Sélectionnez le compte de service par défaut App Engine, puis utilisez le menu d'options à droite pour sélectionner Créer une clé.
- Lorsque vous y êtes invité, sélectionnez JSON comme 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 connexion
Si vous souhaitez écrire des tests complètement hors connexion, vous pouvez initialiser le SDK sans aucun paramètre:
// At the top of test/index.test.js
const test = require('firebase-functions-test')();
Simuler des valeurs de configuration
Si vous utilisez functions.config()
dans le code de vos fonctions, vous pouvez simuler les valeurs de configuration. Par exemple, si functions/index.js
contient le code suivant :
const functions = require('firebase-functions/v1');
const key = functions.config().stripe.key;
Vous pouvez ensuite simuler 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 principal en tant que module. Assurez-vous de ne le faire qu'après avoir initialisé firebase-functions-test
et simulé 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 connexion et que admin.initializeApp()
figure dans le code de vos fonctions, vous devez le remplacer par un bouchon 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');
Tester les fonctions d'arrière-plan (non HTTP)
Pour tester les fonctions autres que HTTP, procédez comme suit:
- Encapsulez la fonction que vous souhaitez tester avec la méthode
test.wrap
. - Créer des données de test
- Appelez la fonction encapsulée avec les données de test que vous avez créées et les champs de contexte d'événement que vous souhaitez spécifier.
- Faire des assertions sur le comportement
Encapsulez d'abord la fonction que vous souhaitez tester. Supposons que vous souhaitiez tester une fonction makeUppercase
dans functions/index.js
. É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
accepte deux paramètres:
- data (obligatoire) : 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 créer des données personnalisées ou des exemples de données. - eventContextOptions (facultatif) : champs du contexte de l'événement que vous souhaitez spécifier. Le contexte de l'événement est le deuxième paramètre envoyé au gestionnaire de fonction que vous avez écrit. Si vous n'incluez pas de paramètre
eventContextOptions
lorsque vous appelezwrapped
, 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 à inclure que les champs que vous souhaitez remplacer. Tous les champs que vous n'avez pas ignoré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
});
Créer des données de test
Le premier paramètre d'une fonction encapsulée correspond aux données de test avec lesquelles appeler la fonction sous-jacente. Il existe plusieurs façons de créer des données de test.
Utiliser des données personnalisées
firebase-functions-test
dispose d'un certain nombre de fonctions permettant de créer les données nécessaires au test de vos fonctions. Par exemple, utilisez test.firestore.makeDocumentSnapshot
pour créer un DocumentSnapshot
Firestore. Le premier argument correspond aux données, le deuxième au chemin d'accès complet de la référence, et un troisième argument facultatif permet de spécifier d'autres propriétés de l'instantané.
// 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 devez créer deux instantanés: l'un pour l'état "avant" et l'autre pour l'état "après". Vous pouvez ensuite 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 documentation de référence de l'API pour connaître les fonctions similaires pour tous les autres types de données.
Utiliser un exemple de données
Si vous n'avez pas besoin de personnaliser les données utilisées dans vos tests, firebase-functions-test
propose des méthodes permettant de 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 documentation de référence de l'API pour obtenir des méthodes permettant d'obtenir des exemples de données pour chaque type de fonction.
Utiliser des données fictives (pour le mode hors connexion)
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 bouchons au lieu de créer un DocumentSnapshot
ou un DataSnapshot
réel.
Supposons que vous écriviez 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); });
Dans 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 brut dans lequel ces deux chemins de code fonctionnent et utilisez 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);
Effectuer des assertions
Après avoir initialisé le SDK, encapsulé les fonctions et construit des données, vous pouvez appeler les fonctions encapsulées avec les données construites et émettre des assertions sur le comportement. Vous pouvez utiliser une bibliothèque telle que Chai pour effectuer ces assertions.
Effectuer des assertions en mode en ligne
Si vous avez initialisé Firebase Test SDK pour Cloud Functions en mode en ligne, vous pouvez vérifier que les actions souhaitées (telles qu'une écriture dans la base de données) ont eu lieu à l'aide du SDK firebase-admin
.
L'exemple ci-dessous indique 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'); }); });
Émettre des assertions en mode hors connexion
Vous pouvez émettre 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 pour affirmer que certaines méthodes ont été appelées, et avec les paramètres attendus.
Tester les fonctions HTTP
Pour tester les fonctions HTTP onCall, utilisez la même approche que pour tester 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 nécessite deux paramètres: un objet de requête et un objet de réponse. Voici comment tester l'exemple de fonction addMessage()
:
- Ignorez la fonction de redirection dans l'objet de réponse, car
sendMessage()
l'appelle. - Dans la fonction de redirection, utilisez chai.assert pour vous aider à émettre des assertions sur les paramètres avec lesquels la fonction de redirection doit être appelée :
// 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);
Test de nettoyage
À la toute fin de votre code de test, appelez la fonction de nettoyage. Cela réinitialise les variables d'environnement définies par le SDK lors de son initialisation et supprime les applications Firebase qui ont pu être 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();
Consulter des exemples complets et en savoir plus
Vous pouvez consulter les exemples complets dans le dépôt GitHub de Firebase.
- Tester les fonctions Realtime Database et HTTP en mode en ligne
- Tester les fonctions Realtime Database et HTTP en mode hors connexion
Pour en savoir plus, consultez la documentation de référence de l'API pour firebase-functions-test
.