Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Pruebas unitarias de Cloud Functions

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

Esta página describe las mejores prácticas y herramientas para escribir pruebas unitarias para sus funciones, como las pruebas que formarían parte de un sistema de integración continua (CI). Para facilitar las pruebas, Firebase proporciona el SDK de prueba de Firebase para Cloud Functions. Se distribuye en npm como firebase-functions-test y es un SDK de prueba complementario de firebase-functions . El SDK de Firebase Test para Cloud Functions:

  • Se encarga de la configuración y el desmontaje apropiados para sus pruebas, como configurar y desactivar las variables de entorno que necesita firebase-functions .
  • Genera datos de muestra y contexto de eventos, para que solo tenga que especificar los campos que son relevantes para su prueba.

Configuración de prueba

Instale firebase-functions-test y Mocha , un marco de prueba, ejecutando los siguientes comandos en su carpeta de funciones:

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

A continuación, cree una carpeta de test dentro de la carpeta de funciones, cree un nuevo archivo dentro para su código de prueba y asígnele un nombre como index.test.js .

Finalmente, modifique functions/package.json para agregar lo siguiente:

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

Una vez que haya escrito las pruebas, puede ejecutarlas ejecutando npm test dentro de su directorio de funciones.

Inicialización del SDK de prueba de Firebase para funciones en la nube

Hay dos formas de usar firebase-functions-test :

  1. Modo en línea (recomendado): escriba pruebas que interactúen con un proyecto de Firebase dedicado a las pruebas para que las escrituras de la base de datos, las creaciones del usuario, etc. sucedan realmente, y su código de prueba pueda inspeccionar los resultados. Esto también significa que otros SDK de Google utilizados en sus funciones también funcionarán.
  2. Modo sin conexión: escriba pruebas unitarias en silos y sin conexión sin efectos secundarios. Esto significa que cualquier llamada a método que interactúe con un producto de Firebase (p. ej., escribir en la base de datos o crear un usuario) debe cerrarse. Por lo general, no se recomienda usar el modo fuera de línea si tiene funciones de Cloud Firestore o Realtime Database, ya que aumenta considerablemente la complejidad de su código de prueba.

Inicializar SDK en modo en línea (recomendado)

Si desea escribir pruebas que interactúen con un proyecto de prueba, debe proporcionar los valores de configuración del proyecto necesarios para inicializar la aplicación a través firebase-admin y la ruta a un archivo de clave de cuenta de servicio.

Para obtener los valores de configuración de su proyecto de Firebase:

  1. Abra la configuración de su proyecto en la consola de Firebase .
  2. En Tus aplicaciones, selecciona la aplicación deseada.
  3. En el panel derecho, seleccione la opción para descargar un archivo de configuración para las aplicaciones de Apple y Android.

    Para aplicaciones web, seleccione Configuración para mostrar los valores de configuración.

Para crear un archivo clave:

  1. Abra el panel Cuentas de servicio de Google Cloud Console.
  2. Seleccione la cuenta de servicio predeterminada de App Engine y use el menú de opciones a la derecha para seleccionar Crear clave .
  3. Cuando se le solicite, seleccione JSON para el tipo de clave y haga clic en Crear .

Después de guardar el archivo clave, inicialice el 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');

Inicializar SDK en modo fuera de línea

Si desea escribir pruebas completamente fuera de línea, puede inicializar el SDK sin ningún parámetro:

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

Burlarse de los valores de configuración

Si usa functions.config() en su código de funciones, puede simular los valores de configuración. Por ejemplo, si functions/index.js contiene el siguiente código:

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

Luego puede simular el valor dentro de su archivo de prueba así:

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

Importando tus funciones

Para importar sus funciones, use require para importar su archivo de funciones principales como un módulo. Asegúrese de hacer esto solo después de inicializar firebase-functions-test y burlarse de los valores de configuración.

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

Si inicializaste firebase-functions-test en modo fuera de línea y tienes admin.initializeApp() en tu código de funciones, entonces debes agregarlo antes de importar tus funciones:

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

Prueba de funciones en segundo plano (no HTTP)

El proceso para probar funciones que no son HTTP implica los siguientes pasos:

  1. Envuelva la función que le gustaría probar con el método test.wrap
  2. Construir datos de prueba
  3. Invoque la función envuelta con los datos de prueba que construyó y cualquier campo de contexto de evento que desee especificar.
  4. Hacer afirmaciones sobre el comportamiento.

Primero ajuste la función que le gustaría probar. Supongamos que tiene una función en functions/index.js llamada makeUppercase , que le gustaría probar. Escriba lo siguiente en functions/test/index.test.js

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

wrapped es una función que invoca makeUppercase cuando se llama. wrapped toma 2 parámetros:

  1. data (requerido): los datos para enviar a makeUppercase . Esto corresponde directamente al primer parámetro enviado al controlador de función que escribió. firebase-functions-test proporciona métodos para construir datos personalizados o datos de ejemplo.
  2. eventContextOptions (opcional): campos del contexto del evento que te gustaría especificar. El contexto del evento es el segundo parámetro enviado al controlador de función que escribió. Si no incluye un parámetro eventContextOptions al llamar a wrapped , aún se genera un contexto de evento con campos sensibles. Puede anular algunos de los campos generados especificándolos aquí. Tenga en cuenta que solo tiene que incluir los campos que desea anular. Se generan todos los campos que no anuló.
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
});

Construcción de datos de prueba

El primer parámetro de una función envuelta son los datos de prueba para invocar la función subyacente. Hay varias maneras de construir datos de prueba.

Uso de datos personalizados

firebase-functions-test tiene una serie de funciones para construir los datos necesarios para probar sus funciones. Por ejemplo, use test.firestore.makeDocumentSnapshot para crear una DocumentSnapshot de Firestore. El primer argumento son los datos, el segundo argumento es la ruta de referencia completa y hay un tercer argumento opcional para otras propiedades de la instantánea que puede especificar.

// 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 está probando una función onUpdate o onWrite , deberá crear dos instantáneas: una para el estado anterior y otra para el estado posterior. Luego, puede usar el método makeChange para crear un objeto Change con estas instantáneas.

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

Consulte la referencia de API para ver funciones similares para todos los demás tipos de datos.

Usando datos de ejemplo

Si no necesita personalizar los datos utilizados en sus pruebas, firebase-functions-test ofrece métodos para generar datos de ejemplo para cada tipo de función.

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

Consulte la referencia de API para obtener métodos para obtener datos de ejemplo para cada tipo de función.

Uso de datos resguardados (para el modo fuera de línea)

Si inicializó el SDK en modo sin conexión y está probando una función de Cloud Firestore o Realtime Database, debe usar un objeto sin formato con stubs en lugar de crear una DocumentSnapshot o DataSnapshot real.

Digamos que está escribiendo una prueba unitaria para la siguiente función:

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

Dentro de la función, snap se usa dos veces:

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

En el código de prueba, cree un objeto sin formato en el que ambas rutas de código funcionen y use Sinon para agregar los métodos.

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

Hacer afirmaciones

Después de inicializar el SDK, envolver las funciones y construir datos, puede invocar las funciones envueltas con los datos construidos y hacer afirmaciones sobre el comportamiento. Puede usar una biblioteca como Chai para hacer estas afirmaciones.

Realización de afirmaciones en modo online

Si inicializaste el SDK de prueba de Firebase para Cloud Functions en modo en línea , puedes afirmar que las acciones deseadas (como una escritura en la base de datos) se realizaron mediante el firebase-admin .

El siguiente ejemplo afirma que 'INPUT' se ha escrito en la base de datos del proyecto de prueba.

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

Hacer afirmaciones en modo fuera de línea

Puede hacer afirmaciones sobre el valor de retorno esperado de la función:

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

También puede usar espías de Sinon para afirmar que se han llamado ciertos métodos y con los parámetros que espera.

Prueba de funciones HTTP

Para probar las funciones HTTP onCall, use el mismo enfoque que para probar las funciones en segundo plano .

Si está probando funciones HTTP onRequest, debe usar firebase-functions-test si:

  • Usas functions.config()
  • Su función interactúa con un proyecto de Firebase u otras API de Google y le gustaría usar un proyecto de Firebase real y sus credenciales para sus pruebas.

Una función HTTP onRequest toma dos parámetros: un objeto de solicitud y un objeto de respuesta. Así es como puede probar la función de ejemplo addMessage() :

  • Anule la función de redirección en el objeto de respuesta, ya que sendMessage() lo llama.
  • Dentro de la función de redirección, use chai.assert para ayudar a hacer afirmaciones sobre con qué parámetros se debe llamar a la función de redirección:
// 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);

Prueba de limpieza

Al final de su código de prueba, llame a la función de limpieza. Esto anula las variables de entorno que el SDK configuró cuando se inicializó y elimina las aplicaciones de Firebase que pueden haberse creado si usó el SDK para crear una base de datos en tiempo real DataSnapshot o Firestore DocumentSnapshot .

test.cleanup();

Revise los ejemplos completos y aprenda más

Puede revisar los ejemplos completos en el repositorio de Firebase GitHub.

Para obtener más información, consulta la referencia de la API para firebase-functions-test .